Skip to content

Instantly share code, notes, and snippets.

@SuaYoo
Last active November 22, 2021 16:15
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SuaYoo/07638d095852f0ce7777cb74ccee9c7b to your computer and use it in GitHub Desktop.
Save SuaYoo/07638d095852f0ce7777cb74ccee9c7b to your computer and use it in GitHub Desktop.
Encrypt and decrypt JSON files for Web3 Storage (requires Node.js >=15.7)
// node >=15.7.0 required for Blob usage
const { Blob } = require('buffer');
const crypto = require('crypto');
const { Web3Storage } = require('web3.storage');
// Secret env variables
//
// Grab token from https://web3.storage/tokens/
const WEB3_STORAGE_API_KEY = 'my_api_key';
// Encryption secret must have exact length of 32
// You'll want to save the output of
// `crypto.randomBytes(16).toString('hex')`
// somewhere instead of re-generating it each time
// so that you can decrypt your files later.
const ENCRYPTION_SECRET = crypto.randomBytes(16).toString('hex');
// Make storage client
const storageClient = new Web3Storage({
token: WEB3_STORAGE_API_KEY,
});
// Encryption options
// Encrypt/decrypt functions based on
// https://attacomsian.com/blog/nodejs-encrypt-decrypt-data
const algorithm = 'aes-256-ctr';
// Encrypt some data
// Example:
// encrypt(JSON.stringify({ a: 'b' }))
function encrypt(text/*: string*/) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algorithm, ENCRYPTION_SECRET, iv);
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
return {
iv: iv.toString('hex'),
content: encrypted.toString('hex'),
};
}
// Decrypt file blob (NOTE only works with node >=15.7.0)
// Example:
// decrypt(await web3Response.files()[0])
async function decrypt(file/*: Blob*/) {
const hash = JSON.parse(await file.text());
const decipher = crypto.createDecipheriv(
algorithm,
ENCRYPTION_SECRET,
Buffer.from(hash.iv, 'hex')
);
const decrypted = Buffer.concat([
decipher.update(Buffer.from(hash.content, 'hex')),
decipher.final(),
]);
return decrypted.toString();
}
// Store object in Web3 Storage as an encrypted JSON file
async function storeEncryptedData(data, fileName) {
const encrypted = encrypt(JSON.stringify(data));
const file = new Blob([JSON.stringify(encrypted)], {
type: 'application/json',
});
file.name = `${fileName}.json.enc`;
const cid = await storageClient.put([file]);
console.log('stored files with cid:', cid);
return cid;
}
// Retrieve encrypted JSON file from Web3 Storage and decrypt
async function retrieveDecryptedData(cid) {
const res = await storageClient.get(cid);
console.log(`Got a response! [${res.status}] ${res.statusText}`);
if (!res.ok) {
throw new Error(`failed to get ${cid} - [${res.status}] ${res.statusText}`);
}
// unpack File objects from the response
const files = await res.files();
return Promise.all(files.map(decrypt));
}
async function demo() {
// Store some data
const cid = await storeEncryptedData({ text: 'secret text' }, 'test_file');
// Retrieve same data
const jsonArr = await retrieveDecryptedData(cid);
console.log('decrypted results:', jsonArr);
}
// Demo:
demo();
@SuaYoo
Copy link
Author

SuaYoo commented Nov 3, 2021

To run the demo, replace WEB3_STORAGE_API_KEY with your actual Web3 Storage token and then:

$ npm install web3.storage
$ node ./web3-storage.js

You should see something like:

> decrypted results: [ '{"text":"secret text"}' ]

You'll want to pass WEB3_STORAGE_API_KEY and ENCRYPTION_SECRET as environment variables during actual usage.


Note: Blob support was added in node v15.7.0, v14.18.0, demo script only tested with v16.7.0

Full write-up here: https://write.as/build-it-nice/encrypting-json-files-for-decentralized-storage

@coding-island-js
Copy link

Is there a way to return the results without encrypting/decrypting? I'm able to store the file into web3.storage, but when I retrieve it without using the encrypting functionality it returns the file name, last modified date, and cid. How do I receive the content of the uploaded file?

@SuaYoo
Copy link
Author

SuaYoo commented Nov 17, 2021

@coding-island-js you could replace with encrypted in line 64 with JSON.stringify(data) and replace decrypt in line 90 with async (file) => JSON.parse(await file.text()). Try this:

async function storeEncryptedData(data, fileName) {
  // const encrypted = encrypt(JSON.stringify(data));
  const file = new Blob([JSON.stringify(data)], {
    type: 'application/json',
  });

  file.name = `${fileName}.json.enc`;

  const cid = await storageClient.put([file]);

  console.log('stored files with cid:', cid);

  return cid;
}

// Retrieve encrypted JSON file from Web3 Storage and decrypt
async function retrieveDecryptedData(cid) {
  const res = await storageClient.get(cid);

  console.log(`Got a response! [${res.status}] ${res.statusText}`);

  if (!res.ok) {
    throw new Error(`failed to get ${cid} - [${res.status}] ${res.statusText}`);
  }

  // unpack File objects from the response
  const files = await res.files();

  // return Promise.all(files.map(decrypt));
  const parseFile = async (file) => JSON.parse(await file.text())
  return Promise.all(files.map(parseFile));
}

I haven't tested actually tested this out, so it may need some more tweaking.

@coding-island-js
Copy link

Awesome. thanks for the code! I did something similar to what you wrote above, but instead did a new File instead of Blob and it works. Again thanks for taking the time to reply back and provide a solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment