├── .gitignore ├── package.json ├── LICENSE ├── examples └── example.js ├── README.md ├── index.js └── tests └── simpleTest.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-sign-and-encrypt", 3 | "version": "1.0.0", 4 | "description": "simple json object signer", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha tests/*.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/MyEtherWallet/JSONSignAndEncrypt.git" 12 | }, 13 | "keywords": [ 14 | "json", 15 | "sign", 16 | "encrypt" 17 | ], 18 | "author": "kvhnuke", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/MyEtherWallet/JSONSignAndEncrypt/issues" 22 | }, 23 | "homepage": "https://github.com/MyEtherWallet/JSONSignAndEncrypt#readme", 24 | "dependencies": { 25 | "crypto": "0.0.3", 26 | "eccrypto": "^1.0.3" 27 | }, 28 | "devDependencies": { 29 | "chai": "^3.5.0", 30 | "mocha": "^3.2.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 MyEtherWallet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | var signUtils = require("../index.js"); 2 | var crypto = require("crypto"); 3 | var objectToEncrypt = { 4 | "glossary": { 5 | "title": "example glossary", 6 | "GlossDiv": { 7 | "title": "S", 8 | "GlossList": { 9 | "GlossEntry": { 10 | "ID": "SGML", 11 | "SortAs": "SGML", 12 | "GlossTerm": "Standard Generalized Markup Language", 13 | "Acronym": "SGML", 14 | "Abbrev": "ISO 8879:1986", 15 | "GlossDef": { 16 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 17 | "GlossSeeAlso": ["GML", "XML"] 18 | }, 19 | "GlossSee": "markup" 20 | } 21 | } 22 | } 23 | } 24 | } 25 | 26 | var clientPrivKey = crypto.randomBytes(32); 27 | var clientpubKey = signUtils.getPublicKey(clientPrivKey); 28 | var serverPrivKey = crypto.randomBytes(32); 29 | var serverPubKey = signUtils.getPublicKey(serverPrivKey); 30 | 31 | //signing and encrypting 32 | signUtils.signAndEncrypt(objectToEncrypt, clientPrivKey, serverPubKey).then(function(encrypted) { 33 | console.log(encrypted); 34 | //decrypting and verifying 35 | signUtils.decryptAndVerify(encrypted, serverPrivKey, clientpubKey).then(function(decrypted) { 36 | //encrypted is the encrypted object 37 | console.log(decrypted); 38 | }).catch(function(err) { 39 | //will throw error on verification failure 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON sigining and encryption 2 | 3 | library to make signing and encrypting json object easier 4 | 5 | # Features! 6 | 7 | - Generate deterministic string to verify based on json and sign the string 8 | - No more signing failures due to JSON object not maintaining the same order 9 | - ECIES encryption/decryption on JSON 10 | 11 | ### Installation 12 | ```sh 13 | $ npm install json-sign-and-encrypt 14 | ``` 15 | ### Examples 16 | 17 | Generate ethereum address: 18 | ``` 19 | var signUtils = require("../index.js"); 20 | var crypto = require("crypto"); 21 | var objectToEncrypt = { 22 | "glossary": { 23 | "title": "example glossary", 24 | "GlossDiv": { 25 | "title": "S", 26 | "GlossList": { 27 | "GlossEntry": { 28 | "ID": "SGML", 29 | "SortAs": "SGML", 30 | "GlossTerm": "Standard Generalized Markup Language", 31 | "Acronym": "SGML", 32 | "Abbrev": "ISO 8879:1986", 33 | "GlossDef": { 34 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 35 | "GlossSeeAlso": ["GML", "XML"] 36 | }, 37 | "GlossSee": "markup" 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | var clientPrivKey = crypto.randomBytes(32); 45 | var clientpubKey = signUtils.getPublicKey(clientPrivKey); 46 | var serverPrivKey = crypto.randomBytes(32); 47 | var serverPubKey = signUtils.getPublicKey(serverPrivKey); 48 | 49 | //signing and encrypting 50 | signUtils.signAndEncrypt(objectToEncrypt, clientPrivKey, serverPubKey).then(function(encrypted) { 51 | console.log(encrypted); 52 | //decrypting and verifying 53 | signUtils.decryptAndVerify(encrypted, serverPrivKey, clientpubKey).then(function(decrypted) { 54 | //encrypted is the encrypted object 55 | console.log(decrypted); 56 | }).catch(function(err) { 57 | //will throw error on verification failure 58 | }); 59 | 60 | }); 61 | ``` 62 | 63 | License 64 | ---- 65 | 66 | MIT 67 | 68 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const crypto = require("crypto"); 2 | const eccrypto = require("eccrypto"); 3 | const signingAlgo = 'sha256'; 4 | var getDeterministicValues = function(JSONObj) { 5 | var keys = []; 6 | for (var key in JSONObj) 7 | if (JSONObj.hasOwnProperty(key)) keys.push(key); 8 | keys.sort(); 9 | var values = []; 10 | for (var i in keys) { 11 | if (typeof JSONObj[keys[i]] === 'object') values.push(getDeterministicValues(JSONObj[keys[i]])); 12 | else values.push(JSONObj[keys[i]]); 13 | } 14 | return values.join(''); 15 | } 16 | var encryptedToHex = function(sig) { 17 | return { 18 | iv: sig.iv.toString('hex'), 19 | ephemPublicKey: sig.ephemPublicKey.toString('hex'), 20 | ciphertext: sig.ciphertext.toString('hex'), 21 | mac: sig.mac.toString('hex') 22 | } 23 | } 24 | var hexToEncrypted = function(sigObj) { 25 | return { 26 | iv: Buffer(sigObj.iv, 'hex'), 27 | ephemPublicKey: Buffer(sigObj.ephemPublicKey, 'hex'), 28 | ciphertext: Buffer(sigObj.ciphertext, 'hex'), 29 | mac: Buffer(sigObj.mac, 'hex') 30 | } 31 | } 32 | var getPublicKey = function(privKey) { 33 | return eccrypto.getPublic(privKey); 34 | } 35 | var JSONObjectToString = function(JSONObj) { 36 | return JSON.stringify(JSONObj); 37 | } 38 | var stringToJSONObject = function(str) { 39 | return JSON.parse(str); 40 | } 41 | var stringToHash = function(str) { 42 | return crypto.createHash(signingAlgo).update(str).digest(); 43 | } 44 | var getSignature = function(str, privKey) { 45 | return eccrypto.sign(privKey, stringToHash(str)).then(function(sig) { 46 | return sig.toString('hex'); 47 | }); 48 | } 49 | var verifySignature = function(str, sig, pubKey) { 50 | sig = Buffer(sig, 'hex'); 51 | return eccrypto.verify(pubKey, stringToHash(str), sig).then(function() { 52 | return true; 53 | }).catch(function() { 54 | return false; 55 | }); 56 | } 57 | var encrypt = function(JSONObj, pubKey) { 58 | var str = JSONObjectToString(JSONObj); 59 | return eccrypto.encrypt(pubKey, Buffer(str)).then(function(encrypted) { 60 | return encryptedToHex(encrypted); 61 | }); 62 | } 63 | var decrypt = function(encryptedStr, privKey) { 64 | encryptedBuffer = hexToEncrypted(encryptedStr); 65 | return eccrypto.decrypt(privKey, encryptedBuffer).then(function(str) { 66 | return stringToJSONObject(str.toString()); 67 | }); 68 | } 69 | var signAndEncrypt = function(JSONObj, privKeyClient, pubKeyServer) { 70 | var detValues = getDeterministicValues(JSONObj); 71 | return getSignature(detValues, privKeyClient).then(function(sig) { 72 | JSONObj.sig = sig; 73 | return encrypt(JSONObj, pubKeyServer).then(function(encrypted) { 74 | return encrypted; 75 | }); 76 | }); 77 | } 78 | var decryptAndVerify = function(encryptedStr, privKeyServer, pubKeyClient) { 79 | encryptedBuffer = hexToEncrypted(encryptedStr); 80 | return eccrypto.decrypt(privKeyServer, encryptedBuffer).then(function(str) { 81 | var JSONObj = stringToJSONObject(str.toString()); 82 | var sig = JSONObj.sig; 83 | delete JSONObj.sig; 84 | var detValues = getDeterministicValues(JSONObj); 85 | return verifySignature(detValues, sig, pubKeyClient).then(function() { 86 | return JSONObj; 87 | }).catch(function() { 88 | return false; 89 | }); 90 | }); 91 | } 92 | module.exports = { 93 | getDeterministicValues: getDeterministicValues, 94 | JSONObjectToString: JSONObjectToString, 95 | stringToJSONObject: stringToJSONObject, 96 | stringToHash: stringToHash, 97 | getSignature: getSignature, 98 | verifySignature: verifySignature, 99 | encrypt: encrypt, 100 | decrypt: decrypt, 101 | signAndEncrypt: signAndEncrypt, 102 | decryptAndVerify: decryptAndVerify, 103 | getPublicKey: getPublicKey 104 | } 105 | -------------------------------------------------------------------------------- /tests/simpleTest.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var signUtils = require("../index.js"); 3 | var crypto = require("crypto"); 4 | var assert = chai.assert; 5 | 6 | describe('deterministic string test', function() { 7 | it('should return sort values by keys', function() { 8 | var testObj1 = { 9 | b: "2", 10 | c: "3", 11 | a: "1", 12 | d: "4" 13 | }; 14 | assert.equal(signUtils.getDeterministicValues(testObj1), '1234'); 15 | }); 16 | it('should return sort values by keys - recursive', function() { 17 | var testObj1 = { 18 | b: "2", 19 | c: "3", 20 | a: { 21 | f: "6", 22 | e: "5", 23 | g: { 24 | h: "8", 25 | i: { 26 | k: "11", 27 | j: "10", 28 | l: "12", 29 | } 30 | }, 31 | }, 32 | d: "4" 33 | }; 34 | assert.equal(signUtils.getDeterministicValues(testObj1), '568101112234'); 35 | }); 36 | }); 37 | describe('hashing alogo test', function() { 38 | it('should hash string', function() { 39 | assert.equal(signUtils.stringToHash("this is epic test string").toString('hex'), '28118cd4b47763e15b4aaf0f34e64dfbc099a744cd2bda6be7257dfb2f6356f6'); 40 | }); 41 | }); 42 | describe('encrypt decrypt test', function() { 43 | it('should properly encrypt and decrypt 100 tests', function(done) { 44 | var privateKeyA = crypto.randomBytes(32); 45 | var publicKeyA = signUtils.getPublicKey(privateKeyA); 46 | var randStrings = {}; 47 | for (var i = 0; i < 100; i++) randStrings[i] = crypto.randomBytes(32).toString('hex'); 48 | signUtils.encrypt(randStrings, publicKeyA).then(function(encrypted) { 49 | signUtils.decrypt(encrypted, privateKeyA).then(function(decrypted) { 50 | for (var key in decrypted) assert.equal(randStrings[key], decrypted[key]); 51 | done(); 52 | }); 53 | }); 54 | }); 55 | it('Another test just to be safe', function(done) { 56 | var privateKeyA = crypto.randomBytes(32); 57 | var publicKeyA = signUtils.getPublicKey(privateKeyA); 58 | var randStrings = {}; 59 | for (var i = 0; i < 100; i++) randStrings[i] = crypto.randomBytes(32).toString('hex'); 60 | signUtils.encrypt(randStrings, publicKeyA).then(function(encrypted) { 61 | signUtils.decrypt(encrypted, privateKeyA).then(function(decrypted) { 62 | for (var key in decrypted) assert.equal(randStrings[key], decrypted[key]); 63 | done(); 64 | }); 65 | }); 66 | }); 67 | }); 68 | describe('signature tests', function() { 69 | it('sign and verify 100 tests', function(done) { 70 | var privateKeyA = crypto.randomBytes(32); 71 | var publicKeyA = signUtils.getPublicKey(privateKeyA); 72 | var randStrings = []; 73 | for (var i = 0; i < 100; i++) randStrings.push(crypto.randomBytes(32).toString('hex')); 74 | var counter = 0; 75 | var _signVerify = function(str, privKey, pubKey) { 76 | signUtils.getSignature(str, privKey).then(function(sig) { 77 | signUtils.verifySignature(str, sig, publicKeyA).then(function(result) { 78 | assert.equal(result, true); 79 | counter++; 80 | if (counter == randStrings.length) done(); 81 | }); 82 | }); 83 | } 84 | for (var i in randStrings) _signVerify(randStrings[i], privateKeyA, publicKeyA); 85 | }); 86 | }); 87 | describe('encrypt and decrypt json object', function() { 88 | it('sign encrypt, decrypt and verify', function(done) { 89 | var clientPrivKey = crypto.randomBytes(32); 90 | var clientpubKey = signUtils.getPublicKey(clientPrivKey); 91 | var serverPrivKey = crypto.randomBytes(32); 92 | var serverPubKey = signUtils.getPublicKey(serverPrivKey); 93 | var randStrings = {}; 94 | for (var i = 0; i < 100; i++) randStrings[crypto.randomBytes(32).toString('hex')] = crypto.randomBytes(32).toString('hex'); 95 | signUtils.signAndEncrypt(randStrings, clientPrivKey, serverPubKey).then(function(encrypted) { 96 | signUtils.decryptAndVerify(encrypted, serverPrivKey, clientpubKey).then(function(result) { 97 | delete randStrings.sig; 98 | for (var key in randStrings) assert.equal(randStrings[key], result[key]); 99 | done(); 100 | }); 101 | }); 102 | }); 103 | }); 104 | --------------------------------------------------------------------------------