Encrypt File from browser AES-GCM - javascript

I have a problem with encrypting and decrypting files with AES GCM, I encrypt/decrypt my files with this code:
I tried many some libraries but they didnt solve my problem.
var FileSaver = require("file-saver");
export function formatBytes(bytes) {
var marker = 1024; // Change to 1000 if required
var decimal = 3; // Change as required
var kiloBytes = marker; // One Kilobyte is 1024 bytes
var megaBytes = marker * marker; // One MB is 1024 KB
var gigaBytes = marker * marker * marker; // One GB is 1024 MB
// return bytes if less than a KB
if (bytes < kiloBytes) return bytes + " Bytes";
// return KB if less than a MB
else if (bytes < megaBytes)
return (bytes / kiloBytes).toFixed(decimal) + " KB";
// return MB if less than a GB
else if (bytes < gigaBytes)
return (bytes / megaBytes).toFixed(decimal) + " MB";
// return GB if less than a TB
else return (bytes / gigaBytes).toFixed(decimal) + " GB";
}
export const encryptFile = async (key, iv, file) => {
return await window.crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: iv
},
key,
file
);
};
/*
Get some key material to use as input to the deriveKey method.
The key material is a password supplied by the user.
*/
export const getKeyMaterial = async (password) => {
let enc = new TextEncoder();
return window.crypto.subtle.importKey(
"raw",
enc.encode(password),
{ name: "PBKDF2" },
false,
["deriveBits", "deriveKey"]
);
};
/*
Given some key material and some random salt
derive an AES-GCM key using PBKDF2.
*/
export const getKey = async (keyMaterial, salt) => {
return window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: salt,
iterations: 100000,
hash: "SHA-256"
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
};
// get the iv which is similar the salt value used for encryption
export const getiv = () => {
return window.crypto.getRandomValues(new Uint8Array(12));
};
// load the file in the memory
export const getFile = async (inputFile) => {
return await inputFile.arrayBuffer();
};
export const decryptFile = (iv, key, cipherText) => {
return window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv
},
key,
cipherText
);
};
export const getDigest = (uid) => {
let enc = new TextEncoder();
return crypto.subtle.digest("SHA-256", enc.encode(uid));
};
export const startEncryption = async (file, password) => {
console.log(file, password);
let digest = getDigest(password);
let rawFile = getFile(file);
let keyMaterial = getKeyMaterial();
Promise.all([digest, rawFile, keyMaterial]).then((values) => {
console.log(values);
let salt = new Uint8Array(
"12345678".match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16);
})
);
let iv = new Uint8Array(
"4142434445464748494a4b4c4d4e4f50"
.match(/[\da-f]{2}/gi)
.map(function (h) {
return parseInt(h, 16);
})
);
// generate a crypto key
getKey(values[2], salt).then((resp) => {
console.log(resp);
encryptFile(resp, iv, values[1]).then((cipherText) => {
let fileBlob = new Blob([cipherText], { type: file.type });
FileSaver.saveAs(fileBlob, file.name);
});
});
});
};
export const startDecryption = (cipherText, password) => {
let keyMaterial = getKeyMaterial();
let digest = getDigest(password);
let rawFile = getFile(cipherText);
Promise.all([digest, rawFile, keyMaterial]).then((values) => {
let salt = new Uint8Array(
"12345678".match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16);
})
);
let iv = new Uint8Array(
"4142434445464748494a4b4c4d4e4f50"
.match(/[\da-f]{2}/gi)
.map(function (h) {
return parseInt(h, 16);
})
);
// generate a crypto key
getKey(values[2], salt).then((resp) => {
decryptFile(iv, resp, values[1]).then((file) => {
let fileBlob = new Blob([file], { type: file.type });
FileSaver.saveAs(fileBlob, file.name);
});
});
});
};
Files can be encrypt/decrypted but they can be decrypted with any password even if it doesnt match the password it encrypted with.
What am i missing here?

you call getKeyMaterial() without the password, so its always undefined.
try getKeyMaterial(password);

Related

crypto.subtle decrypting compact jwe with ECDH-ES+A128KW - The operation failed for an operation-specific reason

I am trying to decrypt a compact JWE formatted response message with the crypto.subtle libraries.
I am sending to the Server my public key in JWK format with curve algo ECDH-ES+A128KW, encryption A256GCM, curve name P-256.
The server sends me back a compact JWE response.
As I understand this flow, it should be something like:
Client sends the public key to the Server
Server responds to client back the compact JWE message
Client derives the shared AES 128 KW key based on servers public key and own private key
Client unwraps the AES 128 GCM key using the shared AES 128 KW key
Clients decrypts the ciphertext using the AES 128 GCM key.
When my code reaches the unwrapKey step, i am only getting the error The operation failed for an operation-specific reason. At the moment I fail to find the problem.
My code looks like this right now:
export const decryptCompactJWE = async (
compactJWE: string,
privateKey: CryptoKey
) => {
const [protectedHeader, encryptedKey, iv, ciphertext, tag] =
compactJWE.split(".");
const header = JSON.parse(Buffer.from(protectedHeader, "base64").toString());
console.log("header:", header);
const publicKey = await crypto.subtle.importKey(
"jwk",
header.epk,
{
name: "ECDH",
namedCurve: "P-256",
},
true,
["deriveKey", "deriveBits"]
);
const derivedKey = await crypto.subtle.deriveKey(
{ name: "ECDH", public: publicKey },
privateKey,
{ name: "AES-KW", length: 128 },
true,
["unwrapKey"]
);
const myJWK = await crypto.subtle.exportKey("jwk", derivedKey);
console.log("jwk", myJWK);
const myAESKey = await crypto.subtle.unwrapKey(
"raw",
Buffer.from(encryptedKey, "base64url"),
derivedKey,
"AES-KW",
{ name: "AES-GCM" },
false,
["decrypt"]
);
console.log(myAESKey);
return crypto.subtle.decrypt(
{ name: "AES-GCM", iv: Buffer.from(iv, "base64url") },
myAESKey,
Buffer.from(ciphertext, "base64url")
);
};
Here is my test data:
const privateKey = {
kty: "EC",
crv: "P-256",
ext: true,
key_ops: ["deriveKey", "deriveBits"],
d: "vPZxnkg-j1xZ_8BZfH6jIvV52NvG2pxsZhmYgI9BEec",
x: "CorZZG9qa5korQ6eVLenbFz2QyGKkpoEYlAJxF1JzGA",
y: "yIEnQSGlMNVp6JEzZO3QvjQ0UDAwepzUZqwgsv0OTQE",
};
const JWE_RESPONSE = "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkExMjhHQ00iLCJraWQiOiJhYmMxMjMiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiNmNReW1GUlJSTjVkVHdoOHA5dWx1NkgwS3paSkRGcm4xdjFKb2NzVURCUSIsInkiOiJTSGliQjFEMnBHMmVMbUxMV09HTTB4UUtCRDFpM3ZtZjJRNjZIM2RnbzJ3IiwiY3J2IjoiUC0yNTYifX0.OwriqBm-PXkIj_QwbqKZRVxql0sja2-p.UrZs5Ixu_rFCxpCw.z9Rfhw.m6AgqKsttsp9TV2dREgbWw";
So far I looked up a all examples I could find to implement this and based on those it kinda looks okay. The debugger is not stepping into the native crypto.subtle code and the error message is also not telling much about what is going wrong for me. The existing examples I found so far, are mostly less complex and skip the key derive part.
WebCrypto is a low level API that in particular does not support JWT/JWS/JWE, so decrypting the token with WebCrypto alone means a corresponding effort, since some functionalities have to be implemented by yourself.
According to the header, the token is encrypted with:
alg: "ECDH-ES+A128KW"
enc: "A128GCM"
Here ECDH-ES+A128KW means that a shared secret is derived with ECDH, from which a wrapping key is determined using Concat KDF. With this key the encrypted key is unwrapped using AES-KW. Finally, the unwrapped key is applied to decrypt the content using AES-128/GCM, see here.
In the posted code Concat KDF is not taken into account. This and some other issues are the reason why decryption fails. Since WebCrypto does not support Concat KDF, a custom implementation is needed (or an additional library), which affects the whole implementation.
The following changes and fixes are required in the individual processing steps:
Deriving the shared secret
One of the inputs to Concat KDF is the shared secret. First, the private and the public key involved are imported. Then the shared secret can be determined most efficiently with deriveBits().
The gives as shared secret (hex encoded):
832bb9a5ac5c1b7febc64ed9522aefedd9f5d62830972224b1226e5498a6d13a
Keep in mind here:
The shared secret for P-256 is 32 bytes in size.
When importing the public key, no key usages may be specified.
(async () => {
// input data: encrypted token and private JWK
const compactJWE = "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkExMjhHQ00iLCJraWQiOiJhYmMxMjMiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiNmNReW1GUlJSTjVkVHdoOHA5dWx1NkgwS3paSkRGcm4xdjFKb2NzVURCUSIsInkiOiJTSGliQjFEMnBHMmVMbUxMV09HTTB4UUtCRDFpM3ZtZjJRNjZIM2RnbzJ3IiwiY3J2IjoiUC0yNTYifX0.OwriqBm-PXkIj_QwbqKZRVxql0sja2-p.UrZs5Ixu_rFCxpCw.z9Rfhw.m6AgqKsttsp9TV2dREgbWw";
const privateKey = {
kty: "EC",
crv: "P-256",
ext: true,
key_ops: ["deriveKey", "deriveBits"],
d: "vPZxnkg-j1xZ_8BZfH6jIvV52NvG2pxsZhmYgI9BEec",
x: "CorZZG9qa5korQ6eVLenbFz2QyGKkpoEYlAJxF1JzGA",
y: "yIEnQSGlMNVp6JEzZO3QvjQ0UDAwepzUZqwgsv0OTQE",
};
const [protectedHeader, encryptedKey, iv, ciphertext, tag] = compactJWE.split(".");
// import private key and public key (header.epk)
const privateCryptoKey = await crypto.subtle.importKey(
"jwk",
privateKey,
{name: "ECDH", namedCurve: "P-256"},
false,
["deriveBits"]
);
const decoder = new TextDecoder();
const header = JSON.parse(decoder.decode(b64url2ab(protectedHeader)));
const publicCryptoKey = await crypto.subtle.importKey(
"jwk",
header.epk,
{name: "ECDH", namedCurve: "P-256"},
false,
[]
);
// ECDH: derive shared secret (size: 32 bytes for P-256)
const sharedSecret = await crypto.subtle.deriveBits(
{ name: "ECDH", public: publicCryptoKey },
privateCryptoKey,
256
);
console.log("ECDH - shared secret: " + ab2hex(sharedSecret)); // ECDH - shared secret: 832bb9a5ac5c1b7febc64ed9522aefedd9f5d62830972224b1226e5498a6d13a
})();
// Helper -------------------------------------------------------------------------------------------------------
function b64url2ab(base64_string){
base64_string = base64_string.replace(/-/g, '+').replace(/_/g, '/');
return Uint8Array.from(window.atob(base64_string), c => c.charCodeAt(0));
}
function ab2hex(ab) {
return Array.prototype.map.call(new Uint8Array(ab), x => ('00' + x.toString(16)).slice(-2)).join('');
}
Determining the wrapping key
From the shared secret the 16 bytes wrapping key can now be derived with Concat KDF. Concat KDF is described in Section 5.8.1 of NIST.800-56A. A JavaScript implementation can be found e.g. here.
Concat KDF has a number of other input data in addition to the shared secret, which are described here and illustrated here with an example. These are:
the algorithm, here: ECDH-ES+A128KW
the length of the output key (in bits), here: 128
the Base64url decoding of the "apu" (Agreement PartyUInfo) header parameter, if present, here: not present
the Base64url decoding of the "apv" (Agreement PartyVInfo) header parameter, if present, here: not present
This gives as wrapping key (hex encoded):
64c845c913d6a61208464a087ce72b81
(async () => {
const sharedSecret = hex2ab("832bb9a5ac5c1b7febc64ed9522aefedd9f5d62830972224b1226e5498a6d13a").buffer;
const encoder = new TextEncoder();
const algorithm = encoder.encode('ECDH-ES+A128KW'); // from header.alg
const keyLength = 128;
const apu = '';
const apv = '';
const otherInfo = concat(
lengthAndInput(algorithm),
lengthAndInput(apu),
lengthAndInput(apv),
uint32be(keyLength),
);
const wrappingKey = await concatKdf(new Uint8Array(sharedSecret), keyLength, otherInfo);
console.log("Concat KDF - wrapping key: " + ab2hex(wrappingKey)); // Concat KDF - wrapping key: 64c845c913d6a61208464a087ce72b81
})();
// Concat KDF implementation -------------------------------------------------------------------------------------------------------
function writeUInt32BE(buf, value, offset) {
buf.set([value >>> 24, value >>> 16, value >>> 8, value & 0xff], offset);
}
function uint32be(value) {
const buf = new Uint8Array(4);
writeUInt32BE(buf, value);
return buf;
}
async function concatKdf(secret, bits, value) {
const iterations = Math.ceil((bits >> 3) / 32);
const res = new Uint8Array(iterations * 32);
for (let iter = 0; iter < iterations; iter++) {
const buf = new Uint8Array(4 + secret.length + value.length);
buf.set(uint32be(iter + 1));
buf.set(secret, 4);
buf.set(value, 4 + secret.length);
res.set(Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', buf))), iter * 32);
}
return res.slice(0, bits >> 3);
}
function concat(...buffers) {
const size = buffers.reduce((acc, { length }) => acc + length, 0);
const buf = new Uint8Array(size);
let i = 0;
buffers.forEach((buffer) => {
buf.set(buffer, i);
i += buffer.length;
});
return buf;
}
function lengthAndInput(input) {
return concat(uint32be(input.length), input);
}
// Helper -------------------------------------------------------------------------------------------------------
function hex2ab(hex){
return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) {return parseInt(h, 16)}));
}
function ab2hex(ab) {
return Array.prototype.map.call(new Uint8Array(ab), x => ('00' + x.toString(16)).slice(-2)).join('');
}
Note that aside from the sample data in this question, the Concat KDF implementation adapted for above code has not been tested further!
Unwrapping of the encrypted key and decryption of the ciphertext
After importing the wrapping key, the encrypted key can be unwrapped (AES-KW). With the unwrapped key the ciphertext can be decrypted (AES-128, GCM).
The gives as decrypted data (UTF-8 decoded):
8807
Note regarding AES/GCM that:
WebCrypto expects as ciphertext the concatenation of the actual ciphertext and the tag.
The token header has to be specified as additional authenticated data (AAD) (otherwise authentication fails).
(async () => {
const wrappingKey = hex2ab("64c845c913d6a61208464a087ce72b81").buffer;
// input data: encrypted token and private JWK
const compactJWE = "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkExMjhHQ00iLCJraWQiOiJhYmMxMjMiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiNmNReW1GUlJSTjVkVHdoOHA5dWx1NkgwS3paSkRGcm4xdjFKb2NzVURCUSIsInkiOiJTSGliQjFEMnBHMmVMbUxMV09HTTB4UUtCRDFpM3ZtZjJRNjZIM2RnbzJ3IiwiY3J2IjoiUC0yNTYifX0.OwriqBm-PXkIj_QwbqKZRVxql0sja2-p.UrZs5Ixu_rFCxpCw.z9Rfhw.m6AgqKsttsp9TV2dREgbWw";
const [protectedHeader, encryptedKey, iv, ciphertext, tag] = compactJWE.split(".");
// Import wrapping key, decrypt wrapped key:
const wrappingCryptoKey = await crypto.subtle.importKey(
"raw",
wrappingKey,
"AES-KW",
false,
["unwrapKey"]
);
const unwrappedCryptoKey = await crypto.subtle.unwrapKey(
"raw",
b64url2ab(encryptedKey),
wrappingCryptoKey,
"AES-KW",
{ name: "AES-GCM" },
false,
["decrypt"]
);
// Decrypt ciphertext
// - Concatenate ciphertext and tag: ciphertext|tag
// - Consider header as AAD
const encoder = new TextEncoder();
const ciphertextAB = new Uint8Array(b64url2ab(ciphertext));
const tagAB = new Uint8Array(b64url2ab(tag));
const ciphertextTag = new Uint8Array(ciphertextAB.length + tagAB.length);
ciphertextTag.set(ciphertextAB);
ciphertextTag.set(tagAB, ciphertextAB.length);
const additionalData = encoder.encode(protectedHeader);
const decryptedText = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: b64url2ab(iv), additionalData: additionalData },
unwrappedCryptoKey,
ciphertextTag
);
const decoder = new TextDecoder();
console.log("Decrypted text: " + decoder.decode(decryptedText)); // Decrypted text: 8807
})();
// Helper -------------------------------------------------------------------------------------------------------
function hex2ab(hex){
return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) {return parseInt(h, 16)}));
}
function b64url2ab(base64_string){
base64_string = base64_string.replace(/-/g, '+').replace(/_/g, '/');
return Uint8Array.from(window.atob(base64_string), c => c.charCodeAt(0));
}
All together:
(async () => {
// input data: encrypted token and private JWK
const compactJWE = "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkExMjhHQ00iLCJraWQiOiJhYmMxMjMiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiNmNReW1GUlJSTjVkVHdoOHA5dWx1NkgwS3paSkRGcm4xdjFKb2NzVURCUSIsInkiOiJTSGliQjFEMnBHMmVMbUxMV09HTTB4UUtCRDFpM3ZtZjJRNjZIM2RnbzJ3IiwiY3J2IjoiUC0yNTYifX0.OwriqBm-PXkIj_QwbqKZRVxql0sja2-p.UrZs5Ixu_rFCxpCw.z9Rfhw.m6AgqKsttsp9TV2dREgbWw";
const privateKey = {
kty: "EC",
crv: "P-256",
ext: true,
key_ops: ["deriveKey", "deriveBits"],
d: "vPZxnkg-j1xZ_8BZfH6jIvV52NvG2pxsZhmYgI9BEec",
x: "CorZZG9qa5korQ6eVLenbFz2QyGKkpoEYlAJxF1JzGA",
y: "yIEnQSGlMNVp6JEzZO3QvjQ0UDAwepzUZqwgsv0OTQE",
};
const [protectedHeader, encryptedKey, iv, ciphertext, tag] = compactJWE.split(".");
// import private key and public key (header.epk)
const privateCryptoKey = await crypto.subtle.importKey(
"jwk",
privateKey,
{name: "ECDH", namedCurve: "P-256"},
false,
["deriveBits"]
);
const decoder = new TextDecoder();
const header = JSON.parse(decoder.decode(b64url2ab(protectedHeader)));
const publicCryptoKey = await crypto.subtle.importKey(
"jwk",
header.epk,
{name: "ECDH", namedCurve: "P-256"},
false,
[]
);
// ECDH: derive shared secret (size: 32 bytes for P-256)
const sharedSecret = await crypto.subtle.deriveBits(
{ name: "ECDH", public: publicCryptoKey },
privateCryptoKey,
256
);
// Concat KDF: determine wrapping key
const encoder = new TextEncoder();
const algorithm = encoder.encode('ECDH-ES+A128KW'); // from header.alg
const keyLength = 128;
const apu = '';
const apv = '';
const otherInfo = concat(
lengthAndInput(algorithm),
lengthAndInput(apu),
lengthAndInput(apv),
uint32be(keyLength),
);
const wrappingKey = await concatKdf(new Uint8Array(sharedSecret), keyLength, otherInfo);
// import wrapping key, decrypt wrapped key:
const wrappingCryptoKey = await crypto.subtle.importKey(
"raw",
wrappingKey,
"AES-KW",
false,
["unwrapKey"]
);
const unwrappedCryptoKey = await crypto.subtle.unwrapKey(
"raw",
b64url2ab(encryptedKey),
wrappingCryptoKey,
"AES-KW",
{ name: "AES-GCM" },
false,
["decrypt"]
);
// decrypt ciphertext
// - Concatenate ciphertext and tag: ciphertext|tag
// - Consider header as AAD
const ciphertextAB = new Uint8Array(b64url2ab(ciphertext));
const tagAB = new Uint8Array(b64url2ab(tag));
const ciphertextTag = new Uint8Array(ciphertextAB.length + tagAB.length);
ciphertextTag.set(ciphertextAB);
ciphertextTag.set(tagAB, ciphertextAB.length);
const additionalData = encoder.encode(protectedHeader);
const decryptedText = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: b64url2ab(iv), additionalData: additionalData },
unwrappedCryptoKey,
ciphertextTag
);
console.log("Decrypted text: " + decoder.decode(decryptedText)); // Decrypted text: 8807
})();
// Concat KDF implementation -------------------------------------------------------------------------------------------------------
function writeUInt32BE(buf, value, offset) {
buf.set([value >>> 24, value >>> 16, value >>> 8, value & 0xff], offset);
}
function uint32be(value) {
const buf = new Uint8Array(4);
writeUInt32BE(buf, value);
return buf;
}
async function concatKdf(secret, bits, value) {
const iterations = Math.ceil((bits >> 3) / 32);
const res = new Uint8Array(iterations * 32);
for (let iter = 0; iter < iterations; iter++) {
const buf = new Uint8Array(4 + secret.length + value.length);
buf.set(uint32be(iter + 1));
buf.set(secret, 4);
buf.set(value, 4 + secret.length);
res.set(Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', buf))), iter * 32);
}
return res.slice(0, bits >> 3);
}
function concat(...buffers) {
const size = buffers.reduce((acc, { length }) => acc + length, 0);
const buf = new Uint8Array(size);
let i = 0;
buffers.forEach((buffer) => {
buf.set(buffer, i);
i += buffer.length;
});
return buf;
}
function lengthAndInput(input) {
return concat(uint32be(input.length), input);
}
// Helper -------------------------------------------------------------------------------------------------------
function b64url2ab(base64_string){
base64_string = base64_string.replace(/-/g, '+').replace(/_/g, '/');
return Uint8Array.from(window.atob(base64_string), c => c.charCodeAt(0));
}

How to send transaction on solana?

So I literally copied from
(https://docs.solana.com/developing/clients/javascript-api)
I need to send a transaction from one wallet to another, easy right? But this doest work here
error
Specifically, the transfer method cannot create an instruction. I've tried everything I can, but it hasn't worked. Please help dear programmers
MY CODE:
async function transfer() {
const provider = await IsConnected();
console.log(provider.publicKey.toString());
const connection = new solanaWeb3.Connection(
"https://api.devnet.solana.com",
"confirmed"
);
var recieverWallet = new solanaWeb3.PublicKey(
"3uePV7kJcT3w5qMLPTHVAdLc72SF2iDRwNcxDE713EMf"
);
// var airdropSignature = await connection.requestAirdrop(
// provider.publicKey,
// solanaWeb3.LAMPORTS_PER_SOL * 0.4
// );
// await connection
// .confirmTransaction(airdropSignature)
// .then(console.log("Airdropped"));
var transaction = new solanaWeb3.Transaction();
transaction.feePayer = await provider.publicKey;
let blockhashObj = await connection.getRecentBlockhash();
transaction.recentBlockhash = await blockhashObj.blockhash;
transaction.add(
await solanaWeb3.SystemProgram.transfer({
fromPubkey: provider.publicKey,
toPubkey: recieverWallet,
lamports: 10000,
})
);
if (transaction) {
console.log("Txn created successfully");
}
let signed = await provider.signTransaction(transaction).serialize();
let signature = await connection.sendRawTransaction(signed);
console.log(signature);
let bConfirmed = await connection.confirmTransaction(signature);
console.log(bConfirmed);
}
CODE WHERE PROBLEM:
static transfer(
params: TransferParams | TransferWithSeedParams,
): TransactionInstruction {
let data;
let keys;
if ('basePubkey' in params) {
const type = SYSTEM_INSTRUCTION_LAYOUTS.TransferWithSeed;
data = encodeData(type, {
lamports: BigInt(params.lamports),
seed: params.seed,
programId: toBuffer(params.programId.toBuffer()),
});
keys = [
{pubkey: params.fromPubkey, isSigner: false, isWritable: true},
{pubkey: params.basePubkey, isSigner: true, isWritable: false},
{pubkey: params.toPubkey, isSigner: false, isWritable: true},
];
} else {
const type = SYSTEM_INSTRUCTION_LAYOUTS.Transfer;
***THERE ->*** data = encodeData(type, {lamports: BigInt(params.lamports)});
keys = [
{pubkey: params.fromPubkey, isSigner: true, isWritable: true},
{pubkey: params.toPubkey, isSigner: false, isWritable: true},
];
}
return new TransactionInstruction({
keys,
programId: this.programId,
data,
});
}
export function encodeData<TInputData extends IInstructionInputData>(
type: InstructionType<TInputData>,
fields?: any,
): Buffer {
const allocLength =
type.layout.span >= 0 ? type.layout.span : Layout.getAlloc(type, fields);
const data = Buffer.alloc(allocLength);
const layoutFields = Object.assign({instruction: type.index}, fields);
type.layout.encode(layoutFields, data);
return data;
}
encode(src, b, offset = 0) {
const firstOffset = offset;
let lastOffset = 0;
let lastWrote = 0;
for (const fd of this.fields) {
let span = fd.span;
lastWrote = (0 < span) ? span : 0;
if (undefined !== fd.property) {
const fv = src[fd.property];
if (undefined !== fv) {
lastWrote = fd.encode(fv, b, offset);
if (0 > span) {
/* Read the as-encoded span, which is not necessarily the
* same as what we wrote. */
span = fd.getSpan(b, offset);
}
}
}
lastOffset = offset;
offset += span;
}
const bigInt =
(length: number) =>
(property?: string): Layout<bigint> => {
const layout = blob(length, property);
const {encode, decode} = encodeDecode(layout);
const bigIntLayout = layout as Layout<unknown> as Layout<bigint>;
bigIntLayout.decode = (buffer: Buffer, offset: number) => {
const src = decode(buffer, offset);
return toBigIntLE(Buffer.from(src));
};
bigIntLayout.encode = (bigInt: bigint, buffer: Buffer, offset: number) => {
const src = toBufferLE(bigInt, length);
return encode(src, buffer, offset);
};
return bigIntLayout;
};
function toBufferLE(num, width) {
{
const hex = num.toString(16);
const buffer = Buffer.from(hex.padStart(width * 2, '0').slice(0, width * 2), 'hex');
buffer.reverse();
return buffer;
}
// Allocation is done here, since it is slower using napi in C
return converter.fromBigInt(num, Buffer.allocUnsafe(width), false);
}
exports.toBufferLE = toBufferLE;

MetaMask - RPC Error: TxGasUtil - Trying to call a function on a non-contract address

I've been working on a JavaScript project that includes a smart contract. Basically the user is able to upload a certificate, and the certificate will be hashed and the hash is stored on the blockchain.
JavaScript Code:
captureFile = (event) => {
event.preventDefault()
//Process file for IPFS
const file = event.target.files[0]
const reader = new window.FileReader()
reader.readAsArrayBuffer(file)
reader.onloadend = () => {
this.setState({buffer: Buffer(reader.result) })
}
}
onSubmit = (event) => {
event.preventDefault()
console.log("submitting the form...")
ipfs.add(this.state.buffer, (error, result) => {
console.log('ipfs result', result)
const certificateHash = result[0].hash
if (error) {
console.error(error)
return
}
this.state.contract.methods.set(certificateHash).send({ from: this.state.account }).then((r) => {
this.setState({ certificateHash })
})
})
}
Smart contract code:
pragma solidity >=0.4.21 <0.6.0;
contract Certificate {
string certificateHash;
//write function
function set(string memory _certificateHash) public {
certificateHash = _certificateHash;
}
//read fucntion
function get() public view returns (string memory){
return certificateHash;
}
}
For testing, I am using Truffle and Ganache. Up until this part, everything is fine. Whenever I upload the file, it is successfully detected by metamask and I am able to confirm the transaction. So I've decided to add 1 more step - Encrypting the file before uploading it into the block chain. I added the following code:
New JS file for encryption methods
const CryptoJS = require("crypto-js");
const passKeyword = CryptoJS.lib.WordArray.random(16);
const passFile = CryptoJS.lib.WordArray.random(16);
const salt = CryptoJS.lib.WordArray.random(16);
const iv = CryptoJS.lib.WordArray.random(16);
const keywordKey = CryptoJS.PBKDF2(passKeyword, salt, {
keySize: 256 / 32
});
const fileKey = CryptoJS.PBKDF2(passFile, salt, {
keySize: 256 / 32
})
var cryptoMethods = {
fileEncryption: function (fileBuffer) {
var toEncFile = CryptoJS.enc.Utf8.parse(fileBuffer);
var encryptedFile = CryptoJS.AES.encrypt(toEncFile, fileKey, {
iv: iv, //offset
mode: CryptoJS.mode.CBC, //encryption mode
padding: CryptoJS.pad.Pkcs7 //padding mode
})
var convEncFile = CryptoJS.enc.Utf8.parse(encryptedFile)
return cryptoMethods.convertWordArrayToUint8Array(convEncFile);
},
//Not yet tested
fileDecryption: function (fileCipher) {
var ciphertextStr = CryptoJS.enc.Base64.parse(fileCipher);
var toDecFile = CryptoJS.enc.Base64.stringify (ciphertextStr);
var decryptedFile = CryptoJS.AES.decrypt (toDecFile, fileKey, {
iv: iv, //offset
mode: CryptoJS.mode.CBC, //encryption mode
padding: CryptoJS.pad.Pkcs7 //padding mode
});
var decryptedFileString = decryptedFile.toString(CryptoJS.enc.Utf8);
return decryptedFileString.toString();
},
convertWordArrayToUint8Array: function (wordArray) {
var len = wordArray.words.length,
u8_array = new Uint8Array(len << 2),
offset = 0, word, i
;
for (i=0; i<len; i++) {
word = wordArray.words[i];
u8_array[offset++] = word >> 24;
u8_array[offset++] = (word >> 16) & 0xff;
u8_array[offset++] = (word >> 8) & 0xff;
u8_array[offset++] = word & 0xff;
}
return u8_array;
},
}
export default cryptoMethods;
Main App.js file:
captureFile = (event) => {
event.preventDefault()
//Process file for IPFS
const file = event.target.files[0]
const reader = new window.FileReader()
reader.readAsArrayBuffer(file)
reader.onloadend = () => {
this.setState({buffer: Buffer(reader.result) })
}
}
onSubmit = (event) => {
event.preventDefault()
console.log("submitting the form...")
var encFile = cryptographicMod.fileEncryption(this.state.buffer);
var convEncFile = new Uint8Array([])
convEncFile = toBuffer(encFile)
console.log(convEncFile)
ipfs.add(convEncFile, (error, result) => {
console.log('ipfs result', result)
const certificateHash = result[0].hash
if (error) {
console.error(error)
return
}
if (this.state.contract) {
this.state.contract.methods.set(certificateHash)
.send({from: this.state.account}).then((r) => {
this.setState({certificateHash})
window.alert('File submitted')
})
} else {
console.log('Contract state is empty')
}
})
}
After implementing the new code and trying to upload a file in the application, I get the following error:
MetaMask - RPC Error: TxGasUtil - Trying to call a function on a non-contract address & Uncaught (in promise) . Metamask is also unable to process the gas fee. Any help is welcomed, thanks!

(NODEJS) AES-256-GCM break pdf,gzip,png encoding after decryption

I wish I had help because i don't know why my implementation of AES-GCM break file encoding.
I have an API that uses 1 function to encrypt/decrypt with AES-256-GCM. (With KEY=buffer of 32 random bytes)
Here is the function:
const aes256gcm = (key) => {
const ALGO = 'aes-256-gcm';
const encrypt = (str) => {
try {
const salt = crypto.randomBytes(64);
const iv = crypto.randomBytes(32);
let derivedkey = crypto.pbkdf2Sync(key, salt, 55000, 32, 'sha512');
const cipher = crypto.createCipheriv(ALGO, derivedkey, iv);
let encrypted = Buffer.concat([cipher.update(str), cipher.final()]);
const tag = cipher.getAuthTag();
let buffer = Buffer.concat([salt, iv, encrypted]);
encrypted = {
tag: tag,
buffer: buffer
}
return encrypted;
} catch (e) {
console.log(e);
}
};
const decrypt = (data, authTag) => {
try {
const salt = data.slice(0, 64);
const iv = data.slice(64, 96);
const text = data.slice(96, data.length);
authTag = new Buffer.from(authTag, 'base64');
let derivedkey = crypto.pbkdf2Sync(key, salt, 55000, 32, 'sha512');
let decipher = crypto.createDecipheriv(ALGO, derivedkey, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(text, 'binary') + decipher.final();
return decrypted;
} catch (e) {
console.log(e);
}
};
return {
encrypt,
decrypt
};
};
With this code i encrypt and write in file the result:
const aesCipher = aes.aes256gcm(aes.loadKey(path.resolve(__dirname, `key`)));
const encrypted = aesCipher.encrypt(file.data);
if (encrypted !== undefined) {
fs.writeFile(`${file.name}.enc`, encrypted.buffer, function (err) {
if (err) return console.log(err);
console.log(`${file.name}.enc successfully created`);
});
}
And finaly with this i decrypt and write the content in a file:
const aesCipher = aes.aes256gcm(aes.loadKey(path.resolve(__dirname, `key`)));
let filename = 'test1.gz';
let authTag = 'puI0FfV4Btiy7iPiZFbwew==';
let encrypted = fs.readFileSync(path.resolve(__dirname, `test1.gz.enc`));
const decrypted = aesCipher.decrypt(encrypted, authTag);
if (decrypted !== undefined) {
const file = fs.createWriteStream(filename);
file.write(new Buffer.from(decrypted, 'ascii'), function (err) {
if (err) return console.log(err);
console.log(`Successfully decrypted`);
file.close();
});
res.send({
status: true,
message: 'File is decrypted',
});
}
Diff of my input/output files :
Diff
So, what am i doing wrong ? Is my encryption process good ? Why this only work well with .txt files ?
Thanks you !
I think a fairly small change to your decrypt function should fix the issue, if you just update it to:
const decrypt = (data, authTag) => {
try {
const salt = data.slice(0, 64);
const iv = data.slice(64, 96);
const text = data.slice(96, data.length);
authTag = new Buffer.from(authTag, 'base64');
let derivedkey = crypto.pbkdf2Sync(key, salt, 55000, 32, 'sha512');
let decipher = crypto.createDecipheriv(ALGO, derivedkey, iv);
decipher.setAuthTag(authTag);
let decrypted = Buffer.concat([decipher.update(text), decipher.final()]);
return decrypted;
} catch (e) {
console.log(e);
}
};
I think the previous implementation was not concatenating the result correctly for non-text files.

php openssl_seal equivalent in Node.js

I have a code snippet in php which I would like to move into node.js but I cannot seem to find the right way to do it.
class EncryptService
{
const PUBLIC_CERT_PATH = 'cert/public.cer';
const PRIVATE_CERT_PATH = 'cert/private.key';
const ERROR_LOAD_X509_CERTIFICATE = 0x10000001;
const ERROR_ENCRYPT_DATA = 0x10000002;
public $outEncData = null;
public $outEnvKey = null;
public $srcData;
public function encrypt()
{
$publicKey = openssl_pkey_get_public(self::PUBLIC_CERT_PATH);
if ($publicKey === false) {
$publicKey = openssl_pkey_get_public("file://".self::PUBLIC_CERT_PATH);
}
if ($publicKey === false) {
$errorMessage = "Error while loading X509 public key certificate! Reason:";
while (($errorString = openssl_error_string())) {
$errorMessage .= $errorString . "\n";
}
throw new Exception($errorMessage, self::ERROR_LOAD_X509_CERTIFICATE);
}
$publicKeys = array($publicKey);
$encData = null;
$envKeys = null;
$result = openssl_seal($this->srcData, $encData, $envKeys, $publicKeys);
if ($result === false)
{
$this->outEncData = null;
$this->outEnvKey = null;
$errorMessage = "Error while encrypting data! Reason:";
while (($errorString = openssl_error_string()))
{
$errorMessage .= $errorString . "\n";
}
throw new Exception($errorMessage, self::ERROR_ENCRYPT_DATA);
}
$this->outEncData = base64_encode($encData);
$this->outEnvKey = base64_encode($envKeys[0]);
}
};
The problem is that I cannot find an implementation of the openssl_sign in Javascript anywhere. I do need to keep this structure because I use both outEncData and outEnvKey.
I managed to find the equivalent implementation of openssl_sign with the crypto package but nothing for openssl_seal.
LE added working solution as an answer
OK I've spent some time to figure this out, in short it is now in the repo: ivarprudnikov/node-crypto-rc4-encrypt-decrypt. But we want to follow SO rules here.
Below assumes that you have public key for signing the generated key and private key for testing if all is great.
Randomly generated secret key used for encryption:
const crypto = require('crypto');
const generateRandomKeyAsync = async () => {
return new Promise((resolve, reject) => {
crypto.scrypt("password", "salt", 24, (err, derivedKey) => {
if (err) reject(err);
resolve(derivedKey.toString('hex'));
});
});
}
Encrypt data with the generated key and then encrypt that key with a given public key. We want to send back both encrypted details and encrypted key as we expect the user on another side to have private key.
const crypto = require('crypto');
const path = require('path');
const fs = require('fs');
const encryptKeyWithPubAsync = async (text) => {
return new Promise((resolve) => {
fs.readFile(path.resolve('./public_key.pem'), 'utf8', (err, publicKey) => {
if (err) throw err;
const buffer = Buffer.from(text, 'utf8');
const encrypted = crypto.publicEncrypt(publicKey, buffer);
resolve(encrypted.toString('base64'));
});
});
}
const encryptStringAsync = async (clearText) => {
const encryptionKey = await generateRandomKeyAsync();
const cipher = await crypto.createCipheriv("RC4", encryptionKey, null);
const encryptedKey = await encryptKeyWithPubAsync(encryptionKey);
return new Promise((resolve, reject) => {
let encryptedData = '';
cipher.on('readable', () => {
let chunk;
while (null !== (chunk = cipher.read())) {
encryptedData += chunk.toString('hex');
}
});
cipher.on('end', () => {
resolve([encryptedKey, encryptedData]); // return value
});
cipher.write(clearText);
cipher.end();
});
}
So now we can encrypt the details:
encryptStringAsync("foo bar baz")
.then(details => {
console.log(`encrypted val ${details[1]}, encrypted key ${details[0]}`);
})
Will print something like:
encrypting foo bar baz
encrypted val b4c6c7a79712244fbe35d4, encrypted key bRnxH+/pMEKmYyvJuFeNWvK3u4g7X4cBaSMnhDgCI9iii186Eo9myfK4gOtHkjoDKbkhJ3YIErNBHpzBNc0rmZ9hy8Kur8uiHG6ai9K3ylr7sznDB/yvNLszKXsZxBYZL994wBo2fI7yfpi0B7y0QtHENiwE2t55MC71lCFmYtilth8oR4UjDNUOSrIu5QHJquYd7hF5TUtUnDtwpux6OnJ+go6sFQOTvX8YaezZ4Rmrjpj0Jzg+1xNGIIsWGnoZZhJPefc5uQU5tdtBtXEWdBa9LARpaXxlYGwutFk3KsBxM4Y5Rt2FkQ0Pca9ZZQPIVxLgwIy9EL9pDHtm5JtsVw==
To test above assumptions it is necessary first to decrypt the key with the private one:
const decryptKeyWithPrivateAsync = async (encryptedKey) => {
return new Promise((resolve) => {
fs.readFile(path.resolve('./private_key.pem'), 'utf8', (err, privateKey) => {
if (err) throw err;
const buffer = Buffer.from(encryptedKey, 'base64')
const decrypted = crypto.privateDecrypt({
key: privateKey.toString(),
passphrase: '',
}, buffer);
resolve(decrypted.toString('utf8'));
});
});
}
After key is decrypted it is possible to decrypt the message:
const decryptWithEncryptedKey = async (encKey, encVal) => {
const k = await decryptKeyWithPrivateAsync(encKey);
const decipher = await crypto.createDecipheriv("RC4", k, null);
return new Promise((resolve, reject) => {
let decrypted = '';
decipher.on('readable', () => {
while (null !== (chunk = decipher.read())) {
decrypted += chunk.toString('utf8');
}
});
decipher.on('end', () => {
resolve(decrypted); // return value
});
decipher.write(encVal, 'hex');
decipher.end();
});
}
Hope this answers the question.
The final and working version that worked for me. My problem was that I used an 128bit random key encrypt the data, instead 256bit worked in the end.
The encryption works in JS and it can be decrypted in php with the openssl_open using your private key, which was what I asked in the original question.
const crypto = require('crypto');
const path = require('path');
const fs = require('fs');
const encryptMessage = (message) => {
const public_key = fs.readFileSync(`${appDir}/certs/sandbox.public.cer`, 'utf8');
const rc4Key = Buffer.from(crypto.randomBytes(32), 'binary');
const cipher = crypto.createCipheriv('RC4', rc4Key, null);
let data = cipher.update(message, 'utf8', 'base64');
cipher.final();
const encryptedKey = crypto.publicEncrypt({
key: public_key,
padding: constants.RSA_PKCS1_PADDING
}, rc4Key);
return {
'data': data,
'env_key': encryptedKey.toString('base64'),
};
};

Categories

Resources