How do I decrypt a Nostr message? - javascript

Here's how to send a private message to a Nostr Pubkey:
const crypto = require("crypto");
const secp = require("noble-secp256k1");
const ourPrivateKey = "";
const ourPubKey = "";
const theirPublicKey = "";
const text = "Hello World";
let sharedPoint = secp.getSharedSecret(ourPrivateKey, "02" + theirPublicKey);
let sharedX = sharedPoint.substr(2, 64);
let iv = crypto.randomFillSync(new Uint8Array(16));
var cipher = crypto.createCipheriv(
"aes-256-cbc",
Buffer.from(sharedX, "hex"),
iv
);
let encryptedMessage = cipher.update(text, "utf8", "base64");
encryptedMessage += cipher.final("base64");
let ivBase64 = Buffer.from(iv.buffer).toString("base64");
let event = {
pubkey: ourPubKey,
created_at: Math.floor(Date.now() / 1000),
kind: 4,
tags: [["p", theirPublicKey]],
content: encryptedMessage + "?iv=" + ivBase64,
};
console.log(event.content);
How would the receiver be able decrypt this once they receive it?

If you don't mind using a library, you can use nostr-tools
import {nip04} from 'nostr-tools'
nip04.decrypt(key1,key2,message)

let encryptedMessageParts = event.content.split("?iv=");
let senderMessage_enctrypted = encryptedMessageParts[0];
let iv_ = Buffer.from(encryptedMessageParts[1], "base64");
sharedPoint = secp.getSharedSecret(ourPrivateKey, "02" + theirPublicKey);
sharedX = sharedPoint.substr(2, 64);
var decipher = crypto.createDecipheriv(
"aes-256-cbc",
Buffer.from(sharedX, "hex"),
iv_
);
let senderMessage_decrypted = decipher.update(
senderMessage_enctrypted,
"base64",
"utf8"
);
senderMessage_decrypted += decipher.final("utf8");
console.log(senderMessage_decrypted);
You can read more about how encrypted DM's work here: https://github.com/nostr-protocol/nips/blob/master/04.md

Related

I'am Getting 'bad decrypt' error In Node.js

const crypto = require('crypto')
class encryptedDataClass {
constructor(massage){
this.algorithm = 'aes-256-cbc'
this.initVector = crypto.randomBytes(16);
this.massage = massage;
this.Securitykey = crypto.randomBytes(32);
}
encrypted(){
const cipher = crypto.createCipheriv(this.algorithm, this.Securitykey, this.initVector);
let encryptedData = cipher.update(this.massage, "utf-8", "hex");
return encryptedData += cipher.final("hex");
}
decrypted(){
const decipher = crypto.createDecipheriv(this.algorithm, this.Securitykey,
this.initVector);
let decryptedData = decipher.update(this.massage, "hex", "utf-8");
return decryptedData += decipher.final("utf-8");
}
}
const secureName = new
encryptedDataClass("850749d212e39c8e24aee37bbb43e3c1eaee69ea592eeaeb93da5c83437f64a0")
console.log(secureName.decrypted())
I created that key using encrypted function but I can't decode that key, I'm getting an error:
06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
How can I fix this error?
As AKX mentioned, you're creating a new IV everytime and when decrypting, you need the original IV. See my solution below. I'm storing the IV and later retrieving it by splitting the string on the "."
const crypto = require("crypto");
const algorithm = "aes-192-cbc"; // algorithm to use
const password = 'defineaPassword';
const key = crypto.scryptSync(password, 'salt', 24); // create key
const iv = crypto.randomBytes(16); // generate different ciphertext everytime
module.exports = {
encryptAndBase64 (messagetext) {
try {
const iv = crypto.randomBytes(16); // generate different ciphertext everytime
const cipher = crypto.createCipheriv(algorithm, key, iv);
const ciphertext = cipher.update(messagetext, 'utf8', 'hex') + cipher.final('hex'); // encrypted text
return `${iv.toString('base64')}.${ciphertext}`;
} catch (e) {
console.log(`error while encrypting: ${e.message}`);
return messagetext;
}
},
decrypt (messagetext) {
const split = messagetext.split('.');
const iv = split[0];
messagetext = split[1];
const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(iv, 'base64'));
return decipher.update(messagetext, 'hex', 'utf8') + decipher.final('utf8'); // deciphered text
},
};

Malformed UTF-8 data when decoding a BASE64 to String with ciphertext

I want to decode a BASE64 to String with ciphertext, key, and iv using Crypto-JS. I get the error Malformed UTF-8 data.
My code:
const CryptoJS = require('crypto-js')
const fs = require('fs');
// BASE64
const str = fs.readFileSync('file.txt','utf8')
const genRanHex = function(_0x8d0344) {
for (var _0x15f7b3 = [], _0x2491b5 = '0123456789abcdef', _0x2b0a63 = 0x0; _0x2b0a63 < _0x8d0344; _0x2b0a63++) _0x15f7b3['push'](_0x2491b5['charAt'](Math['floor'](Math['random']() * _0x2491b5['length'])));
return _0x15f7b3['join']('');
}
var KEYS = genRanHex(0x10) + '3c07f4b0efef700a',
ciphertext = str['substring'](0x18),
key = CryptoJS['enc']['Hex']['parse'](KEYS),
iv = CryptoJS.enc.Base64.parse(str.substring(0x0, 0x18))
try {
const decrypted = CryptoJS.AES.decrypt(ciphertext, key, {iv }).toString(CryptoJS.enc.Utf8);
var originalText = JSON.parse(decrypted)
console.log(originalText)
} catch (err) {
console.error(err)
}

What is the equivalent of this function in Javascript/NodeJs?

This is the function used to encrypt in php
function generatetoken()
{
$token = ['id' => '123456','name' => 'username','email' => 'useremail#example.com','type' => 'user'];
$cipher = "AES-128-CBC";
$plaintext = json_encode($token);
$ivlen = openssl_cipher_iv_length($cipher = "AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, '123456789', $options = OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, '123456789', $as_binary = true);
$ciphertext = base64_encode($iv . $hmac . $ciphertext_raw);
return ciphertext;
}
I have this function to decrypt text in php :
function decodetokeninPhp($request_token)
{
$cipher = "AES-128-CBC";
$c = base64_decode($request_token);
$ivlen = openssl_cipher_iv_length($cipher = "AES-128-CBC");
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len = 32);
$ciphertext_raw = substr($c, $ivlen + $sha2len);
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, '123456789', $options = OPENSSL_RAW_DATA, $iv);
$calcmac = hash_hmac('sha256', $ciphertext_raw, '123456789', $as_binary = true);
if (hash_equals($hmac, $calcmac)) {
return json_encode($original_plaintext);
} else {
return null;
}
}
I want the equivalent of this in javascript/Nodejs, i tried this:
function decode(token){
var password = 'My-key';
const ivLength = 16;
const sha2len = 32;
let replacedToken = token.toString();
const base64decoded = Buffer.from(replacedToken, 'base64').toString('binary');
const iv = replacedToken.substr(0,ivLength);
const hMac= replacedToken.substr( ivLength,sha2len);
const ciphertext_raw = replacedToken.substr(ivLength+sha2len);
var DataEncrypt = ciphertext_raw;
var DataKey = CryptoJS.enc.Utf8.parse(password);
var DataVector = CryptoJS.enc.Utf8.parse(iv);
var decrypted = CryptoJS.AES.decrypt(DataEncrypt, DataKey, { iv: DataVector });
var decrypted = CryptoJS.enc.Utf8.stringify(JSON.stringify(decrypted));
console.log("token decoded"+ decrypted);
}
But the console.log just print "token decoded:", it's empty result :(
Please someone help me i'm going crazy :/
If you want to use the crypto module of NodeJS, the whole CryptoJS part has to be changed. A possible implementation with crypto is:
var crypto = require('crypto');
function decode(token){
var keyDec = Buffer.from('0123456789012345', 'utf8'); // sample key for encryption/decryption
var keyAuth = Buffer.from('0123456789', 'utf8'); // sample key for authentication
var ivLen = 16;
var macLen = 32;
var tokenBuf = Buffer.from(token, 'base64');
var iv = tokenBuf.slice(0, ivLen);
var mac = tokenBuf.slice(ivLen, ivLen + macLen);
var ciphertext = tokenBuf.slice(ivLen + macLen);
// Authenticate
var hmac = crypto.createHmac('sha256', keyAuth);
hmac.update(ciphertext);
var macCalc = hmac.digest();
if (macCalc.equals(mac)) {
// Decrypt, if authentication is successfull
var decipher = crypto.createDecipheriv("AES-128-CBC", keyDec, iv);
var decrypted = decipher.update(ciphertext, '', 'utf-8');
decrypted += decipher.final('utf-8');
return JSON.parse(decrypted);
} else {
console.log("Decryption failed");
}
}
var token = decode('U3pukkS48yeNpsusv43Tmv2AmmDfYVtQ8jPw2izEQ0CVOfutGtA9e3ZWXJo2Ibi2axo31blnW6uq/yCz/KRSltwGhCmwpiHQ8mP5ulMf0Nr9V9Gzr6r+R6y3ZOpzTsV9IEkaKDxZTihfoDAzeyN9LYKS9uUW6URL0Do1HGaZ51o='); // from PHP code
console.log(token);
Here, the ciphertext was generated with the posted PHP code using the sample keys 0123456789012345 and 0123456789 for encryption and authentication, respectively.
I am suspicious of the json_encode() in the PHP code for decryption. Here I would expect a json_decode() and thus, in the NodeJS code a JSON.parse() (but you can modify this as needed).

AES/CBC/PKCS5Padding in NodeJs

I am trying to convert my java code to NodeJs code.
Facing some issues on the encryptions.
Here is my java code: compiled code
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.Base64;
public class AESCBCPKCS5Encryption {
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
public static String encrypt(String message, String key) throws GeneralSecurityException, UnsupportedEncodingException {
if (message == null || key == null) {
throw new IllegalArgumentException("text to be encrypted and key should not be null");
}
Cipher cipher = Cipher.getInstance(ALGORITHM);
byte[] messageArr = message.getBytes();
SecretKeySpec keySpec = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");
byte[] ivParams = new byte[16];
byte[] encoded = new byte[messageArr.length + 16];
System.arraycopy(ivParams, 0, encoded, 0, 16);
System.arraycopy(messageArr, 0, encoded, 16, messageArr.length);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(ivParams));
byte[] encryptedBytes = cipher.doFinal(encoded);
encryptedBytes = Base64.getEncoder().encode(encryptedBytes);
return new String(encryptedBytes);
}
public static String decrypt(String encryptedStr, String key) throws GeneralSecurityException, UnsupportedEncodingException {
if (encryptedStr == null || key == null) {
throw new IllegalArgumentException("text to be decrypted and key should not be null");
}
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec keySpec = new
SecretKeySpec(Base64.getDecoder().decode(key), "AES");
byte[] encoded = encryptedStr.getBytes();
encoded = Base64.getDecoder().decode(encoded);
byte[] decodedEncrypted = new byte[encoded.length - 16];
System.arraycopy(encoded, 16, decodedEncrypted, 0, encoded.length - 16);
byte[] ivParams = new byte[16];
System.arraycopy(encoded, 0, ivParams, 0, ivParams.length);
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(ivParams));
byte[] decryptedBytes = cipher.doFinal(decodedEncrypted);
return new String(decryptedBytes);
}
public static void main(String[] args) throws GeneralSecurityException, UnsupportedEncodingException {
String str = "<Request xmlns=\"http://www.kotak.com/schemas/CorpCCPaymentsOTP/CorpCCPaymentsOTP.xsd\" ><username>ENKASH</username><password>Corp#123</password><SrcAppCd>ENKA SH</SrcAppCd><RefNo>Ref1111111111</RefNo><CardNo>4280933990002698</CardNo ><OTP>12345</OTP></Request>";
String key = "e3a74e3c7599f3ab4601d587bd2cc768";
String enc = encrypt(str, key);
System.out.println(enc);
String dec = decrypt(enc, key);
System.out.println(dec);
}
}
Here is my javascript code:
var crypto = require('crypto');
function getAlgorithm(keyBase64) {
var key = Buffer.from(keyBase64, 'base64');
switch (key.length) {
case 16:
return 'aes-128-cbc';
case 32:
return 'aes-256-cbc';
}
throw new Error('Invalid key length: ' + key.length);
}
function encrypt(plainText, keyBase64, ivBase64) {
const key = Buffer.from(keyBase64, 'base64');
const iv = Buffer.from(ivBase64, 'base64');
const cipher = crypto.createCipheriv(getAlgorithm(keyBase64), key, iv);
let encrypted = cipher.update(plainText, 'utf8', 'base64');
encrypted += cipher.final('base64');
return encrypted;
}
function decrypt(messagebase64, keyBase64, ivBase64) {
const key = Buffer.from(keyBase64, 'base64');
const iv = Buffer.from(ivBase64, 'base64');
const decipher = crypto.createDecipheriv(getAlgorithm(keyBase64), key, iv);
let decrypted = decipher.update(messagebase64, 'base64');
decrypted += decipher.final();
return decrypted;
}
var keyBase64 = "DWIzFkO22qfVMgx2fIsxOXnwz10pRuZfFJBvf4RS3eY=";
var ivBase64 = 'e3a74e3c7599f3ab4601d587bd2cc768';
var plainText = '<Request xmlns="http://www.kotak.com/schemas/CorpCCPaymentsOTP/CorpCCPaymentsOTP.xsd"><username>ENKASH</username><password>Corp#123</password><SrcAppCd>ENKA SH</SrcAppCd><RefNo>Ref1111111111</RefNo><CardNo>4280933990002698</CardNo ><OTP>12345</OTP></Request>';
var cipherText = encrypt(plainText, keyBase64, ivBase64);
var decryptedCipherText = decrypt(cipherText, keyBase64, ivBase64);
console.log('Algorithm: ' + getAlgorithm(keyBase64));
console.log('Plaintext: ' + plainText);
console.log('Ciphertext: ' + cipherText);
console.log('Decoded Ciphertext: ' + decryptedCipherText);
Encryptions throwing errors,
What I am doing wrong here?
Initialization vector length of AES in CBC mode is 16 Bytes, code in JAVA takes first 16 bytes of IV however for node.js code it uses all 24 bytes of IV, hence an error. try this code
var crypto = require('crypto');
function getAlgorithm(keyBase64) {
var key = Buffer.from(keyBase64, 'base64');
switch (key.length) {
case 16:
return 'aes-128-cbc';
case 32:
return 'aes-256-cbc';
}
throw new Error('Invalid key length: ' + key.length);
}
function encrypt(plainText, keyBase64, ivBase64) {
const key = Buffer.from(keyBase64, 'base64');
const iv = Buffer.from(ivBase64, 'base64');
const cipher = crypto.createCipheriv(getAlgorithm(keyBase64), key, iv.slice(0, 16));
let encrypted = cipher.update(plainText, 'utf8', 'base64');
encrypted += cipher.final('base64');
return encrypted;
}
function decrypt(messagebase64, keyBase64, ivBase64) {
const key = Buffer.from(keyBase64, 'base64');
const iv = Buffer.from(ivBase64, 'base64');
const decipher = crypto.createDecipheriv(getAlgorithm(keyBase64), key, iv.slice(0, 16));
let decrypted = decipher.update(messagebase64, 'base64');
decrypted += decipher.final();
return decrypted;
}
var keyBase64 = "DWIzFkO22qfVMgx2fIsxOXnwz10pRuZfFJBvf4RS3eY=";
var ivBase64 = 'e3a74e3c7599f3ab4601d587bd2cc768';
var plainText = '<Request xmlns="http://www.kotak.com/schemas/CorpCCPaymentsOTP/CorpCCPaymentsOTP.xsd"><username>ENKASH</username><password>Corp#123</password><SrcAppCd>ENKA SH</SrcAppCd><RefNo>Ref1111111111</RefNo><CardNo>4280933990002698</CardNo ><OTP>12345</OTP></Request>';
var cipherText = encrypt(plainText, keyBase64, ivBase64);
var decryptedCipherText = decrypt(cipherText, keyBase64, ivBase64);
console.log('Algorithm: ' + getAlgorithm(keyBase64));
console.log('Plaintext: ' + plainText);
console.log('Ciphertext: ' + cipherText);
console.log('Decoded Ciphertext: ' + decryptedCipherText);
in java key converted in 16 byte so 'AES/CBC/PKCS5Padding' algo applied while in nodejs this is convered into 24 byte so we need to apply 'aes-192-cbc'.
var crypto = require('crypto');
const encrypt = (plainText, keyBase64) =>{
const textBuffer = Buffer.from(plainText);
const key = Buffer.from(keyBase64, 'base64');
var iv = Buffer.from([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
var encoded = Buffer.concat([iv,textBuffer]);
const cipher = crypto.createCipheriv('aes-192-cbc', key, iv);
let encrypted = cipher.update(encoded, 'binary', 'base64')
encrypted += cipher.final('base64');
return encrypted;
};
const decrypt = (messagebase64, keyBase64) =>{
const key = Buffer.from(keyBase64, 'base64');
const encoded = Buffer.from(messagebase64, 'base64');
var iv = encoded.slice(0, 16);
var decoded = encoded.slice(16,encoded.length);
const decipher = crypto.createDecipheriv('aes-192-cbc', key, iv);
let decrypted = decipher.update(decoded, 'binary','utf-8');
decrypted += decipher.final();
return decrypted;
}
String str = "<Request xmlns=\"http://www.kotak.com/schemas/CorpCCPaymentsOTP/CorpCCPaymentsOTP.xsd\" ><username>ENKASH</username><password>Corp#123</password><SrcAppCd>ENKA SH</SrcAppCd><RefNo>Ref1111111111</RefNo><CardNo>4280933990002698</CardNo ><OTP>12345</OTP></Request>";
String key = "e3a74e3c7599f3ab4601d587bd2cc768";
console.log("input: "+str);
const hash = encrypt(str,key);
console.log("encrypted::: " + hash);
const text = decrypt(hash,key);
console.log("output: "+text);
This is the solution for the above issue. I was getting the decryption error whenever I encrypt using the above code. I made a small update in the code by adding 16 characters at the start of plain text while encrypting.
var crypto = require('crypto');
function getAlgorithm(keyBase64) {
var key = Buffer.from(keyBase64, 'base64');
switch (key.length) {
case 16:
return 'aes-128-cbc';
case 32:
return 'aes-256-cbc';
}
throw new Error('Invalid key length: ' + key.length);
}
function encrypt(plainText, keyBase64, ivBase64) {
const key = Buffer.from(keyBase64, 'base64');
const iv = Buffer.from(ivBase64, 'base64');
const cipher = crypto.createCipheriv(getAlgorithm(keyBase64), key, iv);
let encrypted = cipher.update(iv.subarray(0, 16) + plainText, 'utf8', 'base64')
encrypted += cipher.final('base64');
return encrypted;
};
function decrypt(messagebase64, keyBase64, ivBase64) {
const key = Buffer.from(keyBase64, 'base64');
const iv = Buffer.from(ivBase64, 'base64');
const decipher = crypto.createDecipheriv(getAlgorithm(keyBase64), key, iv);
let decrypted = decipher.update(messagebase64, 'base64');
decrypted += decipher.final();
return decrypted;
}
var keyBase64 = "DWIzFkO22qfVMgx2fIsxOXnwz10pRuZfFJBvf4RS3eY=";
var ivBase64 = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).toString('base64');;
var plainText = '<Request xmlns="http://www.kotak.com/schemas/CorpCCPaymentsOTP/CorpCCPaymentsOTP.xsd"><username>ENKASH</username><password>Corp#123</password><SrcAppCd>ENKA SH</SrcAppCd><RefNo>Ref1111111111</RefNo><CardNo>4280933990002698</CardNo ><OTP>12345</OTP></Request>';
var cipherText = encrypt(plainText, keyBase64, ivBase64);
var decryptedCipherText = decrypt(cipherText, keyBase64, ivBase64);
console.log('Algorithm: ' + getAlgorithm(keyBase64));
console.log('Plaintext: ' + plainText);
console.log('Ciphertext: ' + cipherText);
console.log('Decoded Ciphertext: ' + decryptedCipherText);

How do I create an encrypt and decrypt function in NodeJS?

I need to create an encryption and decryption function in my NodeJS application. Can anyone help and point me in the right direction?
After a bit of digging, I was able to answer my own question... Sorry for the lack of clarity and detail. Will work on that for the next question.
I've included the functions to encrypt and decrypt, and the "helping" functions to generate a key and generate the initialization vector as well.
var crypto = require('crypto');
var encrypt = function encrypt(input, password) {
var key = generateKey(password);
var initializationVector = generateInitializationVector(password);
var data = new Buffer(input.toString(), 'utf8').toString('binary');
var cipher = crypto.createCipheriv('aes-256-cbc', key, initializationVector.slice(0,16));
var encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
var encoded = new Buffer(encrypted, 'binary').toString('base64');
return encoded;
};
var decrypt = function decrypt(input, password) {
var key = generateKey(password);
var initializationVector = generateInitializationVector(password);
var input = input.replace(/\-/g, '+').replace(/_/g, '/');
var edata = new Buffer(input, 'base64').toString('binary');
var decipher = crypto.createDecipheriv('aes-256-cbc', key, initializationVector.slice(0,16));
var decrypted = decipher.update(edata, 'hex', 'utf8');
decrypted += decipher.final('utf8');
var decoded = new Buffer(decrypted, 'binary').toString('utf8');
return decoded;
};
var generateKey = function generateKey(password) {
var cryptographicHash = crypto.createHash('md5');
cryptographicHash.update(password);
key = cryptographicHash.digest('hex');
return key;
}
var generateInitializationVector = function generateInitializationVector(password) {
var cryptographicHash = crypto.createHash('md5');
cryptographicHash.update(password + key);
initializationVector = cryptographicHash.digest('hex');
return initializationVector;
}
var password = 'MyPassword';
var originalStr = 'hello world!';
var encryptedStr = encrypt(originalStr, password);
var decryptedStr = decrypt(encryptedStr, password);
Hats off to adviner for the inspiration of the solution.
His post can be here: here
I had originally gotten this working with this post by dave, but that didn't work for input values with a character length greater than 15. The updated code above works for inputs of any length.

Categories