Nodejs elliptic verification wrong - node.js

I have created some data to encrypt to make sure that the data can't be modified. Using elliptic I created the data then sign it. Then I modify the data. When I verify the data integrity using the generated signature, it returns true.
Am I confused about the use of elliptic?
Here is the code:
const EC = require("elliptic").ec
const ec = new EC("secp256k1")
class Wallet {
constructor(data, pubKey) {
this.keyPair = ec.genKeyPair();
if (typeof (pubKey) !== "undefined")
this.publicKey = pubKey
else
this.publicKey = this.keyPair.getPublic().encode("hex")
this.data = data
this.creationDate = Date.now()
}
toString() {
console.log("public key: " + this.publicKey)
}
sign() {
let raw = this.publicKey + this.data + this.creationDate;
console.log("signing data: " + raw)
return this.keyPair.sign(raw)
}
static verify(wallet, signature) {
let raw = wallet.publicKey + wallet.data + wallet.creationDate;
console.log("verifying data: " + raw)
return ec.keyFromPublic(wallet.publicKey, "hex").verify(raw, signature)
}
}
module.exports = Wallet
The index file:
const Wallet = require("./Wallet")
let wallet = new Wallet("hello");
wallet.toString()
let signature = wallet.sign()
console.log("\n\nSignature: "+JSON.stringify(signature)+"\n\n")
wallet.data = "world"
console.log("wallet data: "+ wallet.data)
console.log("Verify: "+ (Wallet.verify(wallet, signature)))
console.log("\n\n---------------Another wallet hacking-------------\n\n")
let wallet2 = new Wallet("bar", wallet.publicKey);
wallet2.toString()
let signature2 = wallet2.sign()
console.log("\n\nSignature: "+JSON.stringify(signature)+"\n\n")
console.log("wallet data: "+ wallet2.data)
console.log("Verify: "+ (Wallet.verify(wallet2, signature)))
The output:
public key: 045f349291f48e979d5895dedd523ceadf5f60b8b7e87706565f45d73561b1ba4e836ff6d7f27283cb0fece3a7b08abf37db995eae49666314f5b01954e21958fc
signing data: 045f349291f48e979d5895dedd523ceadf5f60b8b7e87706565f45d73561b1ba4e836ff6d7f27283cb0fece3a7b08abf37db995eae49666314f5b01954e21958fchello1604430087928
Signature: {"r":"9ed97af6f4f3becdfa910c91d4865b9c8d9a317ac47b4b7edbd5d4873ca3b3c3","s":"46e76b801de77ee596a7726b08b1db0cdbd9a6b0404bee7c49be0fccee85e99f","recoveryParam":0}
wallet data: world
verifying data: 045f349291f48e979d5895dedd523ceadf5f60b8b7e87706565f45d73561b1ba4e836ff6d7f27283cb0fece3a7b08abf37db995eae49666314f5b01954e21958fcworld1604430087928
Verify: true
---------------Another wallet hacking-------------
public key: 045f349291f48e979d5895dedd523ceadf5f60b8b7e87706565f45d73561b1ba4e836ff6d7f27283cb0fece3a7b08abf37db995eae49666314f5b01954e21958fc
signing data: 045f349291f48e979d5895dedd523ceadf5f60b8b7e87706565f45d73561b1ba4e836ff6d7f27283cb0fece3a7b08abf37db995eae49666314f5b01954e21958fcbar1604430087974
Signature: {"r":"9ed97af6f4f3becdfa910c91d4865b9c8d9a317ac47b4b7edbd5d4873ca3b3c3","s":"46e76b801de77ee596a7726b08b1db0cdbd9a6b0404bee7c49be0fccee85e99f","recoveryParam":0}
wallet data: bar
verifying data: 045f349291f48e979d5895dedd523ceadf5f60b8b7e87706565f45d73561b1ba4e836ff6d7f27283cb0fece3a7b08abf37db995eae49666314f5b01954e21958fcbar1604430087974
Verify: true

Thanks to the #Topaco answer. I understand elliptic does not sign any kind of content. I also cast the string to an array and elliptic still sign and verify the content wrongly.
It considered
["0","4","3","7","d","0","6","2","d","a","a","7","a","9","5","3","f","a","5","6","5","9","3","2","3","d","d","d","0","7","6","4","e","b","8","a","2","e","9","1","9","6","b","4","d","1","f","6","c","1","8","5","0","9","3","5","a","6","0","b","9","a","6","1","5","6","4","d","1","8","a","5","9","c","b","d","e","2","8","f","6","1","9","5","7","1","0","1","c","4","2","f","b","1","2","b","b","4","2","4","1","3","a","4","3","e","e","4","3","6","8","8","f","1","9","d","a","3","6","f","c","9","1","c","6","1","9","9","6","7","h","e","l","l","o","1","6","0","4","4","4","7","6","8","8","9","2","8"]
the same as
["0","4","3","7","d","0","6","2","d","a","a","7","a","9","5","3","f","a","5","6","5","9","3","2","3","d","d","d","0","7","6","4","e","b","8","a","2","e","9","1","9","6","b","4","d","1","f","6","c","1","8","5","0","9","3","5","a","6","0","b","9","a","6","1","5","6","4","d","1","8","a","5","9","c","b","d","e","2","8","f","6","1","9","5","7","1","0","1","c","4","2","f","b","1","2","b","b","4","2","4","1","3","a","4","3","e","e","4","3","6","8","8","f","1","9","d","a","3","6","f","c","9","1","c","6","1","9","9","6","7","w","o","r","l","d","1","6","0","4","4","4","7","6","8","8","9","2","8"]
The first one contains hello and the second one contains world.
I fixed it by passing a sha256 encoded string to sign and to verify.
I installed the package crypto-js then I encode the string:
const Sha256 = require("crypto-js/sha256")
// Signing
const encContent = Sha256(raw).toString()
this.keyPair.sign(encContent )
// Verifying
const encContent = Sha256(raw).toString()
ec.keyFromPublic(wallet.publicKey, "hex").verify(encContent , signature)

Related

How to encrypt and decrypt a number using same key?

I need a way to encrypt a number (say 3423234234) using a secret key, which can also be decrypted using that same key.
const encrypted = encrypt(number, key)
const decrypted = decrypt(encrypted, key)
I tried CryptoJS using AES, DES, Rabbit and RC4 algorithms, but it gives me a long encrypted value with special characters.
I want some encrypted value like MongoDB's ObjectId (which contains only alphanumeric characters like 1575866cab3f22f0c8510451f293f405, and should not exceed 20 or 40 characters).
I figured out a way.
const decrypt = function (num) {
const base = "tGVwFAPiJqh3maNHdZfby9eIQXcr2zvgD57YMn8KWCxkSUujpB10sL6oRTl4OE"
num = num.toString()
let lastChar, baseConverted = 0, index=0;
while(num !== ''){
lastChar = num.slice(-1);
baseConverted += base.indexOf(lastChar) * Math.pow(62,index);
index++;
num = num.slice(0, num.length-1);
}
return baseConverted;
};
const encrypt = function (num) {
const base = "tGVwFAPiJqh3maNHdZfby9eIQXcr2zvgD57YMn8KWCxkSUujpB10sL6oRTl4OE"
// console.log(base.length)
let mod, baseConverted = '';
while(num){
mod = num%62;
baseConverted = base.slice(mod, mod+1)+baseConverted;
num = Math.floor(num/62);
}
return baseConverted;
};
module.exports = {
encrypt,
decrypt
}
The reason I wanted it because I want to encrypt all the id's of a resource before sharing it with client.
It will decrypt the keys when request is received.

Node.js crypto create ecdh with the tron public address

I am aiming for wallet address encryption, using the TronWeb.createAccount(), I fetch the public address for wallet in base58 and the private key as hex.
Sample Public Address: TPeGpPdJGQoNobV4SEjXLdrjefN3iCAAAA
Sample Private Key: 6B07B82D50B27171F35BF1DEAB14...
I am getting the keys using following code.
const TronWeb = require('tronweb');
function createAccount() {
try {
const tronWeb = new TronWeb(fullNode, solidityNode, eventServer);
return tronWeb.createAccount();
} catch (error) {
console.log(error);
throw error;
}
}
When I use the getPublicKey() method after setting the private key in bob.createECDH() the code works fine but in actual I will not have the utility of setPrivateKey() method for alice when I am on bob side. So I will have to pass the base58 public address instead of bob.getPublicKey() or alice.getPublicKey() on either side.
const alice_secret = alice.computeSecret('HEX_PUBLIC_KEY','hex');
Following is the full code for encryption and decryption.
const alice = crypto.createECDH('secp256k1');
const bob = crypto.createECDH('secp256k1');
bob.setPrivateKey("PRIVATE_KEY_FOR_BOB", "hex");
alice.setPrivateKey("PRIVATE_KEY_FOR_ALICE", "hex");
const alice_secret = alice.computeSecret(bob.getPublicKey());
console.log("alice's shared Key: " + alice_secret.toString('hex') + "\n");
var algo = 'aes-256-ecb', plainText = "Some secret to share bob";
var cipher = crypto.createCipher(algo, alice_secret)
var encrypted = cipher.update(plainText, 'utf8', 'hex')
encrypted += cipher.final('hex');
console.log("Encrypted: " + encrypted);
const bob_secret = bob.computeSecret(alice.getPublicKey());
console.log("bob's shared Key: " + bob_secret.toString('hex') + "\n");
var decipher = crypto.createDecipher(algo, bob_secret)
var decrypted = decipher.update(encrypted, 'hex', 'utf8')
decrypted += decipher.final('utf8');
console.log("Decrypted: " + decrypted);
if (plainText == decrypted) {
console.log("ECDH Success")
}
The output is expected when I use setPrivateKey() and then use getPublicKey()
alice's shared Key: 238c3eba08585a5cae1006710c79fe2de329545e9ca4c1ef719c53b55eb337b6
app.js:21 Encrypted: 44184052d9e205fd855aaf5f30b5f186c4bab88a5cfdce58d99cd8c696954c8dd5676807e6fe372fbe3ca5b230e54293
app.js:29 bob's shared Key: 238c3eba08585a5cae1006710c79fe2de329545e9ca4c1ef719c53b55eb337b6
app.js:35 Decrypted: QmdUuJDvgZ7EWEpJmEcFCoYwotn9CHyvK4qEhZs82AhZoQ
app.js:40 ECDH Success
When I convert the public key to hex using bs58 or any other package it says
UnhandledPromiseRejectionWarning: Error: Failed to translate Buffer to a EC_POINT
Is there a way to convert this public address and use it in this situation?
I had to study the ECDH supported key format and regenerate the keys according to new format to be able to fix this issue.
There were two formats of the public key that we can use to encrypt data.

How to verify an ES256 JWT token using Web Crypto when public key is distributed in PEM?

I need to verify ES256 JWT tokens in a Cloudflare Worker. To my understanding they do not run Node.js there and I will have to make everything work with the Web Crypto API (available in browsers, too, as window.crypto.subtle). However, I am expecting to receive the public keys as PEM files and I think I am having problems importing them.
I have been trying to modify an existing open source JWT implementation that only supports HS256 to support ES256.
In addition to trying to use the actual tokens and keys, and keys generated in browsers and OpenSSL, I have tried to use a working example from the JWT.io website (after converting it to JWK format using node-jose), since it should validate correctly. But I am not getting any errors, my code running in browser just tells me the token is not valid.
Here is my Node REPL session I used for converting the key from JWT.io to JWK:
> const jose = require('node-jose')
> const publicKey = `-----BEGIN PUBLIC KEY-----
... MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
... q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
... -----END PUBLIC KEY-----`
> const keyStore = jose.JWK.createKeyStore()
> keyStore.add(publicKey, 'pem')
> keyStore.toJSON()
{
keys: [
{
kty: 'EC',
kid: '19J8y7Zprt2-QKLjF2I5pVk0OELX6cY2AfaAv1LC_w8',
crv: 'P-256',
x: 'EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84',
y: 'kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY'
}
]
}
>
Here is the failing validation, based on code from webcrypto-jwt package:
function utf8ToUint8Array(str) {
// Adapted from https://chromium.googlesource.com/chromium/blink/+/master/LayoutTests/crypto/subtle/hmac/sign-verify.html
var Base64URL = {
stringify: function (a) {
var base64string = btoa(String.fromCharCode.apply(0, a));
return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
},
parse: function (s) {
s = s.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '');
return new Uint8Array(Array.prototype.map.call(atob(s), function (c) { return c.charCodeAt(0); }));
}
};
str = btoa(unescape(encodeURIComponent(str)));
return Base64URL.parse(str);
}
var cryptoSubtle = (crypto && crypto.subtle) ||
(crypto && crypto.webkitSubtle) ||
(window.msCrypto && window.msCrypto.Subtle);
// Token from JWT.io
var tokenParts = [
'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9',
'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0',
'tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA'
];
// Public key from JWT.io converted in Node using node-jose
var publicKey = {
kty: 'EC',
kid: '19J8y7Zprt2-QKLjF2I5pVk0OELX6cY2AfaAv1LC_w8',
crv: 'P-256',
x: 'EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84',
y: 'kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY'
};
var importAlgorithm = {
name: 'ECDSA',
namedCurve: 'P-256',
hash: 'SHA-256',
};
cryptoSubtle.importKey(
"jwk",
publicKey,
importAlgorithm,
false,
["verify"]
).then(function (key) {
var partialToken = tokenParts.slice(0,2).join('.');
var signaturePart = tokenParts[2];
cryptoSubtle.verify(
importAlgorithm,
key,
utf8ToUint8Array(signaturePart),
utf8ToUint8Array(partialToken)
).then(function (ok) {
if (ok) {
console.log("I think it's valid");
} else {
console.log("I think it isn't valid");
}
}).catch(function (err) {
console.log("error verifying", err);
});
}).catch(function(err) {
console.log("error importing", err);
});
Since I copied a valid key and a valid token from JWT.io, I am expecting the code to log "I think it's valid" without errors. It does not show any errors, indeed, but it ends up in "I think it isn't valid" branch.
Answered here How to verify a signed JWT with SubtleCrypto of the Web Crypto API?
So, if I understood correctly, the problem was that base64 encoding included in the open source upstream just does not work correctly in one of the directions, since it uses the browser's btoa. Adapting https://github.com/swansontec/rfc4648.js instead works.

How to make Diffie Hellman connection between client(CryptoJS) and server(Crypto Node.js)

I'm trying to make connection between server and client. I create ECDH keys with elliptic.js and try to cipher-decipher messages. Secret keys are equal both on server and on client. Between servers everything is ok, but between server and client there are a lot of problems.
I have worked a lot to make the client to decipher messages from server. But I can't make right ciphering on client. Server doesn't understand it. Even the client doesn't understand it.
My client uses Crypto JS (I tried to use forge and sjcl but they was much herder for me to understand) and server uses Crypto.
There are my functions on client:
cipherData(data, secret){
let encrypted = CryptoJS.AES.encrypt(data, secret).toString();
return encrypted;
}
decipherData(encryptedData, secret) {
let data;
try {
//make tranformations because of little features in crypto (node) - it uses empty salt array
let ct = CryptoJS.enc.Hex.parse(encryptedData);
let salt = CryptoJS.lib.WordArray.create(0); // empty array
data = CryptoJS.AES.decrypt({ciphertext: ct, salt: salt}, secret);
data = data.toString(CryptoJS.enc.Utf8);
} catch (e) {
console.log('Error decrypting data: ' + e)
}
return data;
}
There is my code on server:
cipherData(data, secret, algorithm = 'aes256'){
const cipher = crypto.createCipher(algorithm, secret);
let encrypted = cipher.update(data,'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}
decipherData(encryptedData, secret, algorithm = 'aes256'){
const decipher = crypto.createDecipher(algorithm, secret);
let data = decipher.update(encryptedData,'hex', 'utf8');
data += decipher.final('utf8');
return data;
}
Maybe someone could help me?
Computed secret key for example(in hex): e6922091e78adce7cff10e01b4eb949317e56ece3597a7daa23c819c6882a955
After many attempts I desided to remake server-side using Crypto-JS too. That decision was completely right. All working fine.
cipherData(data, secret) {
let encrypted = CryptoJS.AES.encrypt(data, secret).toString();
let b64 = CryptoJS.enc.Base64.parse(encrypted);
encrypted = b64.toString(CryptoJS.enc.Hex);
return encrypted;
}
decipherData(encryptedData, secret) {
let data;
try {
let b64 = CryptoJS.enc.Hex.parse(encryptedData);
let bytes = b64.toString(CryptoJS.enc.Base64);
data = CryptoJS.AES.decrypt(bytes, secret);
data = data.toString(CryptoJS.enc.Utf8);
} catch (e) {
console.log('Error decrypting data: ' + e)
}
return data;
}

Using node.js crypto to verify signatures

I am trying to use AWS lambda to verify signatures created with sec256r1 in swift.
Message: "some text to sign"
Has been hashed with sha256 too
signatures will be in base64
encoding:MEYCIQCPfWhpzxMqu3gZWflBm5V0aetgb2/S+SGyGcElaOjgdgIhALaD4lbxVwa8HUUBFOLz+CGvIioDkf9oihSnXHCqh8yV
and public key will look like so:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXIvPbzLjaPLd8jgiv1TL/X8PXpJN
gDkGRj9U9Lcx1yKURpQFVavcMkfWyO8r7JlZNMax0JKfLZUM1IePRjHlFw==
-----END PUBLIC KEY-----
To clarify,
I am trying to use lambda to verify signatures that come from the client side, and encrypt data with their public key if need be.
Here is code:
const crypto = require('crypto');
const verify = crypto.createVerify('SHA256');
verify.write('some text to sign');
verify.end();
const l1 = "-----BEGIN PUBLIC KEY-----\n"
const l2 =
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXIvPbzLjaPLd8jgiv1TL/X8PXpJNgDkGRj9U9Lcx1yKURpQFVavcMkfWyO8r7JlZNMax0JKfLZUM1IePRjHlFw=="
const l3 = "\n-----END PUBLIC KEY-----"
const publicKey = l1 + l2 + l3
const signature = "MEYCIQCPfWhpzxMqu3gZWflBm5V0aetgb2/S+SGyGcElaOjgdgIhALaD4lbxVwa8HUUBFOLz+CGvIioDkf9oihSnXHCqh8yV";
console.log(verify.verify(publicKey, signature));// Prints: true or false
Here's how to inegrate with Nodejs.Crypto. First, the RSA private and public keys need to be generated. There are several ways to do that, here's an a way to do this online with encrypt.JS. You can use getSignatureByInput function below after private and public keys have been stored into the filesystem which generates a unique signature given a string input:
const crypto = require('crypto')
const fs = require('fs')
const getSignatureByInput = (input) => {
let privatePem = fs.readFileSync('PRIVATE_KEY_FILE_PATH_GOES_HERE')
let key = privatePem.toString('ascii')
let sign = crypto.createSign('RSA-SHA256')
sign.update(input)
let signature = sign.sign(key, 'hex')
return signature
}
Thereafter, to verify a signature, you can use the following function:
const getSignatureVerifyResult = (input) => {
let signatureSignedByPrivateKey = getSignatureByInput(input)
let pem = fs.readFileSync('PUBLIC_KEY_FILE_PATH_GOES_HERE')
let publicKey = pem.toString('ascii')
const verifier = crypto.createVerify('RSA-SHA256')
verifier.update(input, 'ascii')
const publicKeyBuf = new Buffer(publicKey, 'ascii')
const signatureBuf = new Buffer(signatureSignedByPrivateKey, 'hex')
const result = verifier.verify(publicKeyBuf, signatureBuf)
return result;
}
getSignatureVerifyResult will return true/false depending on whether the signature are verified. Keep in mind that there's a plethora of algorithms to choose when it comes to signing.
Please see the fuller solution at this StackOverflow post which shows how to use the verify.update() and verify.verify() methods in node.js.

Resources