Last active
April 14, 2022 23:42
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.