Skip to content

Instantly share code, notes, and snippets.

@vinarmani
Last active April 14, 2022 23:42
Show Gist options
  • Save vinarmani/f396c206babafa0f565f1592e667d2ae to your computer and use it in GitHub Desktop.
Save vinarmani/f396c206babafa0f565f1592e667d2ae to your computer and use it in GitHub Desktop.
Quick and dirty DRM for JPEGs leveraging ECIES (encryption and decryption) with Bitcoin private and public keys - Node.js
const fs = require('fs');
const eccryptoJS = require('eccrypto-js');
const coinkey = require('coinkey');
const convertToEncryptStruct = (encbuf) => {
let offset = 0;
let tagLength = 32;
let pub;
switch(encbuf[0]) {
case 4:
pub = encbuf.slice(0, 65);
break;
case 3:
case 2:
pub = encbuf.slice(0, 33);
break;
default:
throw new Error('Invalid type: ' + encbuf[0]);
}
offset += pub.length;
let c = encbuf.slice(offset, encbuf.length - tagLength);
let ivbuf = c.slice(0, 128 / 8);
let ctbuf = c.slice(128 / 8);
let d = encbuf.slice(encbuf.length - tagLength, encbuf.length);
return {
iv: ivbuf,
ephemPublicKey: pub,
ciphertext: ctbuf,
mac: d
}
}
const filedir = './files/'
const filename = 'image.jpg'
const data = fs.readFileSync(`${filedir}${filename}`);
console.log('total file length', data.length)
const headBytes = 50;
const head = data.slice(0, headBytes);
const body = data.slice(headBytes);
console.log('head length', head.length);
console.log('body length', body.length);
// Encrypt with eccrypto-js
const wif = coinkey.createRandom().privateWif;
console.log('wif', wif);
const keypair = coinkey.fromWif(wif);
console.log(keypair.publicKey);
(async () => {
try {
const structuredEj = await eccryptoJS.encrypt(keypair.publicKey, head)
let encryptedEj = Buffer.concat([structuredEj.ephemPublicKey, structuredEj.iv, structuredEj.ciphertext, structuredEj.mac])
// First two bytes of file is length of cyphertext
const encLen = Buffer.alloc(2);
encLen.writeUInt16LE(encryptedEj.length);
console.log('encryptedEj.length', encryptedEj.length);
console.log('encLen', encLen);
// write the body file with the encrypted header piece
const encFileBuf = Buffer.concat([encLen, encryptedEj, body]);
fs.writeFileSync(`${filedir}enc_${filename}`, encFileBuf);
fs.writeFileSync(`${filedir}body_${filename}`, body);
// Decrypt header and stich it back onto the body
const extractLen = encFileBuf.slice(0, 2).readUInt16LE();
console.log('extractLen', extractLen);
const extractEj = encFileBuf.slice(2, 2 + extractLen);
const extractBody = encFileBuf.slice(2 + extractLen);
const encStruct = convertToEncryptStruct(extractEj);
const privKeyBuf = keypair.privateKey;
const headBuf = await eccryptoJS.decrypt(privKeyBuf, encStruct);
const stitchedFile = Buffer.concat([headBuf, extractBody]);
console.log('stitchedFile.length', stitchedFile.length);
fs.writeFileSync(`${filedir}stitch_${filename}`, stitchedFile);
} catch (e) {
console.error(e);
}
})();
@vinarmani
Copy link
Author

As a possible application, you could make the body portion of the file publicly available. No file viewer or editor will be able to view or manipulate the file as it is missing key header data.

The header data that we encode, in the above example, is only the first 50 bytes (less can be encoded and still have the same effect) so we can take the encryptedEj (~180 bytes) and share it in the OP_RETURN of a BCH/XEC/BSV transaction sent to the address associated with the public key we used for the encryption

The end user, upon receiving the transaction, takes the OP_RETURN data, decrypts it, and appends the decrypted header data to the body file. The file is now restored to its original state.

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