কল্পনা করুন আপনি একজন বন্ধুকে একটি গোপন বার্তা পাঠাতে চান, কিন্তু আপনি যে চ্যানেলটি ব্যবহার করতে চান সেটি আপোস করা হয়েছে এবং পর্যবেক্ষণ করা হয়েছে। আপনি কিছু এনক্রিপশন ব্যবহার করতে পারেন, কিন্তু এটি আপনার কথোপকথন নিরীক্ষণ করা লোকেদের সন্দেহ জাগিয়ে তুলবে, তাই আপনাকে অন্য কিছু ব্যবহার করতে হবে।
আজকাল, স্টেগানোগ্রাফি হল গোপন বার্তাগুলিকে অন্য, অ-গোপন ফাইলে (যেমন একটি বিড়ালের ছবি) লুকিয়ে রাখার একটি পদ্ধতি, যাতে আপনি যদি সেই ফাইলটি পাঠান তবে এটি সনাক্ত করা যাবে না। স্টেগ্যানোগ্রাফি শুধুমাত্র ছবিতে টেক্সট লুকানোর মধ্যে সীমাবদ্ধ নয় এবং সাধারণত "অন্য গোপন বার্তা বা শারীরিক বস্তুতে গোপন তথ্য লুকিয়ে রাখা" মানে: আপনি অডিও, ভিডিও বা অন্যান্য টেক্সটে কিছু বার্তা লুকিয়ে রাখতে পারেন, উদাহরণস্বরূপ, কলামার স্থানান্তর।
স্টেগানোগ্রাফি অন্যান্য অনেক ক্ষেত্রেও অত্যন্ত কার্যকর হতে পারে, উদাহরণস্বরূপ, এটি ফুটো থেকে রক্ষা করার জন্য সংবেদনশীল নথিতে জলছাপ করার একটি ভাল বিকল্প হতে পারে।
ইমেজে তথ্য লুকানোর অনেক উপায় আছে, ফাইলের শেষে টেক্সট যোগ করা থেকে শুরু করে মেটাডেটাতে লুকানো পর্যন্ত। এই নিবন্ধে, আমি স্টেগানোগ্রাফির একটি আরও উন্নত পদ্ধতি কভার করতে চাই, বাইনারি স্তরে গিয়ে এবং চিত্রের সীমানার মধ্যে বার্তাগুলি লুকিয়ে রাখি।
আমার স্টেগানোগ্রাফির উদাহরণের জন্য, আমি জাভাস্ক্রিপ্ট ব্যবহার করার সিদ্ধান্ত নিয়েছি কারণ এটি একটি শক্তিশালী প্রোগ্রামিং ভাষা যা একটি ব্রাউজারে কার্যকর করা যেতে পারে।
আমি একটি সাধারণ ইন্টারফেস নিয়ে এসেছি যা ব্যবহারকারীদের এটিতে লুকানো বার্তা পড়ার জন্য একটি চিত্র আপলোড করতে বা একটি চিত্রের মধ্যে একটি বার্তা এনকোড করতে দেয়৷
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Steganography</title> <style> body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; } textarea { width: 300px; height: 100px; } button { margin: 10px; } #outputImage { margin-top: 20px; max-width: 100%; } </style> </head> <body> <h1>Steganography Example</h1> <input type="file" id="upload" accept="image/*"><br> <canvas id="canvas" style="display:none;"></canvas><br> <textarea id="text" placeholder="Enter text to encode"></textarea><br> <button id="encode">Encode Text</button> <button id="decode">Decode Text</button> <p id="decodedText"></p> <img id="outputImage" alt="Output Image"> <script src="./script.js"></script> </body> </html>
এটি ব্যবহার করার জন্য, ব্যবহারকারীরা কেবল একটি চিত্র নির্বাচন করতে পারেন যা তারা ম্যানিপুলেট করতে চান এবং হয় এটি থেকে কিছু পাঠ্য ডিকোড করার চেষ্টা করুন, অথবা এটি এনকোড করুন এবং পরে ছবিটি ডাউনলোড করুন।
জাভাস্ক্রিপ্টে ইমেজ নিয়ে কাজ করতে, আমরা ক্যানভাস API ব্যবহার করতে পারি। এটি চিত্র, অ্যানিমেশন বা এমনকি গেমের গ্রাফিক্স এবং ভিডিওগুলি পরিচালনা এবং অঙ্কন করার জন্য বিভিন্ন ফাংশন সরবরাহ করে।
ক্যানভাস API প্রাথমিকভাবে 2D গ্রাফিক্সের জন্য ব্যবহৃত হয়। আপনি যদি 3D, হার্ডওয়্যার-অ্যাক্সিলারেটেড গ্রাফিক্স নিয়ে কাজ করতে চান, আপনি WebGL API ব্যবহার করতে পারেন (যা ঘটনাক্রমে <canvas> উপাদানও ব্যবহার করে)।
const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); const image = new Image();
ফাইল সিস্টেম থেকে ইমেজ ফাইলটি পড়তে এবং ক্যানভাস প্রসঙ্গে যোগ করতে, আমরা FileReader API ব্যবহার করতে পারি। এটি আমাদেরকে কাস্টম লাইব্রেরির প্রয়োজন ছাড়াই ব্যবহারকারীর কম্পিউটারে সংরক্ষিত যেকোনো ফাইলের বিষয়বস্তু সহজেই পড়তে দেয়।
function handleFileUpload(event) { const reader = new FileReader(); reader.onload = function (e) { image.src = e.target.result; image.onload = function () { canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); }; }; reader.readAsDataURL(event.target.files[0]); }
এটি একটি ফাইল পড়ে এবং সেই ফাইলটিতে থাকা চিত্রটিকে আমাদের পূর্বে সংজ্ঞায়িত 2D ক্যানভাস প্রসঙ্গে আঁকে, তারপরে, আমরা হয় সেই চিত্রটিতে কিছু পাঠ্য এনকোড করতে পারি বা চিত্র থেকে পাঠ্য পড়ার চেষ্টা করতে পারি।
ছবিগুলি পিক্সেল দিয়ে তৈরি, এবং প্রতিটি পিক্সেল এর রং সম্পর্কে তথ্য ধারণ করে। উদাহরণস্বরূপ, যদি একটি ছবি RGBA মডেল ব্যবহার করে এনকোড করা হয়, প্রতিটি পিক্সেলে এটি কতটা লাল, সবুজ, নীল এবং আলফা (অস্বচ্ছতা) উপস্থাপন করে সে সম্পর্কে 4 বাইট তথ্য থাকবে।
একটি ছবিতে কিছু পাঠ্য এনকোড করতে, আমরা এই চ্যানেলগুলির মধ্যে একটি ব্যবহার করতে পারি (উদাহরণস্বরূপ, আলফা চ্যানেল)। যেহেতু এই তথ্যটি বাইনারি সিস্টেমে (যেমন 01001100) উপস্থাপিত হয়, তাই আমরা শেষ বিটটি আমাদের যা প্রয়োজন তাতে পরিবর্তন করতে পারি। এটিকে ন্যূনতম গুরুত্বপূর্ণ বিট (এলএসবি) বলা হয় এবং এটি পরিবর্তন করলে ইমেজেই ন্যূনতম পরিবর্তন ঘটে, যা এটিকে মানুষের থেকে আলাদা করা যায় না।
এখন, কল্পনা করুন আমাদের কাছে "হ্যালো" এর মতো পাঠ্য আছে এবং আমরা এটিকে একটি ছবিতে এনকোড করতে চাই। এটি করতে অ্যালগরিদম হবে
"হ্যালো" পাঠ্যকে বাইনারিতে রূপান্তর করুন।
ইমেজ ডেটার বাইটের মাধ্যমে পুনরাবৃত্তি করুন এবং সেই বাইটের LSB-কে বাইনারি টেক্সট থেকে কিছুটা প্রতিস্থাপন করুন (প্রতিটি পিক্সেলে প্রতিটি রঙের জন্য 4 বাইট ডেটা থাকে, আমার উদাহরণে, আমি ছবির অপাসিটি চ্যানেল পরিবর্তন করতে চাই , তাই আমি প্রতি 4র্থ বাইটে পুনরাবৃত্তি করব)।
বার্তার শেষে একটি নাল বাইট যোগ করুন যাতে ডিকোডিং করার সময় আমরা জানি কখন থামতে হবে।
ইমেজ নিজেই পরিবর্তিত ইমেজ বাইট প্রয়োগ করুন.
প্রথমত, আমরা ব্যবহারকারীর কাছ থেকে যে টেক্সটটি এনকোড করতে চাই তা নিতে হবে এবং এর উপর কিছু মৌলিক যাচাইকরণ করতে হবে।
const text = document.getElementById("text").value; if (!text) { alert("Please enter some text to encode."); return; }
তারপর, আমাদের পাঠ্যটিকে বাইনারিতে রূপান্তর করতে হবে এবং যে চিত্রটিতে আমরা এই পাঠ্যটিকে এনকোড করতে যাচ্ছি তার একটি ক্যানভাস তৈরি করতে হবে।
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imgData.data; let binaryText = ""; for (let i = 0; i < text.length; i++) { let binaryChar = text.charCodeAt(i).toString(2).padStart(8, "0"); binaryText += binaryChar; }
এটি করার জন্য, আমরা সহজভাবে প্রতিটি অক্ষরের উপর পুনরাবৃত্তি করতে পারি এবং charCodeAt
ফাংশন ব্যবহার করে একটি ইউনিকোড সূচক পেতে পারি। এই ইউনিকোডটি তারপর বাইনারিতে রূপান্তরিত হয় এবং প্যাড করা হয় যাতে এটি অন্য যেকোনো অক্ষরের মতো একই দৈর্ঘ্য হয়।
উদাহরণস্বরূপ, ইউনিকোডে "H" অক্ষরটি 72 হিসাবে উপস্থাপন করা হয়েছে; তারপরে আমরা এই সংখ্যাটিকে বাইনারি (1001000) তে রূপান্তর করি এবং শুরুতে 0s যোগ করি (01001000) নিশ্চিত করতে যে সমস্ত অক্ষর একই দৈর্ঘ্যের (8 বিট) হবে।
তারপরে, বার্তাটির শেষে একটি নাল বাইট যোগ করতে হবে যাতে আমরা এটিকে ডিক্রিপ্ট করার সময় বাস্তব টেক্সট এবং ছবির র্যান্ডম পিক্সেল ডেটার মধ্যে পার্থক্য করতে পারি।
binaryText += "00000000";
তারপরে, আমাদের কিছু মৌলিক বৈধতা নিশ্চিত করতে হবে যে ছবিটিতে আমাদের বার্তা এনকোড করার জন্য পর্যাপ্ত পিক্সেল রয়েছে যাতে এটি উপচে না যায়।
if (binaryText.length > data.length / 4) { alert("Text is too long to encode in this image."); return; }
এবং তারপর সবচেয়ে আকর্ষণীয় অংশ আসে, বার্তা এনকোডিং. আমরা আগে যে ডেটা অ্যারে সংজ্ঞায়িত করেছি তাতে চিত্রের প্রতিটি পিক্সেলের জন্য RGBA মানের আকারে পিক্সেল তথ্য রয়েছে। সুতরাং, যদি ছবিটি RGBA এনকোড করা হয়, তবে এতে প্রতিটি পিক্সেল ডেটা অ্যারের 4টি মান দ্বারা উপস্থাপিত হবে; প্রতিটি মান প্রতিনিধিত্ব করে যে পিক্সেলে কতটা লাল, সবুজ এবং নীল আছে।
for (let i = 0; i < binaryText.length; i++) { data[i * 4] = (data[i * 4] & 0b11111110) | parseInt(binaryText[i]); } ctx.putImageData(imgData, 0, 0); const outputImage = document.getElementById("outputImage"); outputImage.src = canvas.toDataURL();
উপরের কোডে, আমরা আমাদের বাইনারি-এনকোড করা পাঠ্যের উপর পুনরাবৃত্তি করি। data[i * 4]
একটি বাইট খুঁজে পায় যা আমাদের পরিবর্তন করতে হবে, এবং যেহেতু আমরা শুধুমাত্র একটি নির্দিষ্ট চ্যানেলের বাইটগুলি পরিবর্তন করতে চাই, আমরা এটি অ্যাক্সেস করতে i
কে 4
দ্বারা গুণ করি।
data[i * 4] & 0b11111110
অপারেশন সর্বনিম্ন উল্লেখযোগ্য বিট 0
এ সেট করে। উদাহরণস্বরূপ, যদি data[i * 4]
বাইনারিতে 10101101
হয়, তাহলে 10101101 & 11111110
অপারেশনের ফলাফল 10101100
হয়। এটি নিশ্চিত করে যে আমরা এটির সাথে আরও কোনও ম্যানিপুলেশন করার আগে LSB 0
এ সেট করা আছে।
parseInt(binaryText[i])
হল বাইনারি এনকোডেড স্ট্রিং থেকে একটি বর্তমান বিট; এটি হয় 1
বা 0
। তারপরে আমরা বিটওয়াইজ বা ( |
) অপারেশন ব্যবহার করে এই বিটটিকে LSB-এ সেট করতে পারি। উদাহরণস্বরূপ, যদি bitwise OR এর বাম অংশ 10101100
হয় এবং binaryText[i]
1
হয়, তাহলে 10101100 | 00000001
ফলে 10101101
হবে। যদি বর্তমান বিট 0
হয়, তাহলে OR-এর ফলাফল 10101100
হবে। এই কারণেই আমাদের প্রথমে এলএসবি মুছে ফেলতে হয়েছিল।
একবার বার্তাটি এনকোড হয়ে গেলে, আমরা এটিকে বর্তমান ক্যানভাসে রাখতে পারি এবং canvas.toDataURL
পদ্ধতি ব্যবহার করে এটিকে HTML-এ রেন্ডার করতে পারি।
একটি ছবি ডিকোড করার প্রক্রিয়া আসলে এনকোডিংয়ের চেয়ে অনেক সহজ। যেহেতু আমরা ইতিমধ্যেই জানি যে আমরা শুধুমাত্র আলফা চ্যানেলকে এনকোড করেছি, তাই আমরা প্রতি 4র্থ বাইটের উপর পুনরাবৃত্তি করতে পারি, শেষ বিটটি পড়তে পারি, এটিকে আমাদের চূড়ান্ত স্ট্রিংয়ে সংযুক্ত করতে পারি এবং বাইনারি থেকে এই ডেটাটিকে একটি ইউনিকোড স্ট্রিংয়ে রূপান্তর করতে পারি।
প্রথমত, আমাদের ভেরিয়েবল শুরু করতে হবে। যেহেতু imgData
ইতিমধ্যেই ইমেজ ইনফরমেশন দিয়ে পূর্ণ হয়েছে (আমরা যখনই ফাইল সিস্টেম থেকে একটি ফাইল পড়ি তখন আমরা ctx.drawImage
বলি), আমরা এটিকে ডাটা ভেরিয়েবলে এক্সট্রাক্ট করতে পারি।
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imgData.data; let binaryText = ""; let decodedText = "";
তারপর আমাদের ইমেজের প্রতি 4র্থ বাইটের উপর পুনরাবৃত্তি করতে হবে, শেষ বিটটি পড়তে হবে এবং এটিকে binaryText
ভেরিয়েবলের সাথে সংযুক্ত করতে হবে।
for (let i = 0; i < data.length; i += 4) { binaryText += (data[i] & 1).toString(); }
data[i]
হল এনকোড করা বাইট, এবং LSB বের করতে আমরা bitwise AND ( &
) অপারেটর ব্যবহার করতে পারি। এটি দুটি মান নেয় এবং সংশ্লিষ্ট বিটের প্রতিটি জোড়ায় একটি AND অপারেশন করে। data[i]
1
এর সাথে তুলনা করে, আমরা মূলত পিক্সেল তথ্য থেকে সবচেয়ে কম উল্লেখযোগ্য বিটকে বিচ্ছিন্ন করি এবং যদি LSB 1
হয়, তাহলে এই ধরনের অপারেশনের ফলাফল 1
হয়। যদি LSB 0
হয়, ফলাফলও 0
হবে।
একবার আমরা সমস্ত LSB পড়ে ফেলি এবং সেগুলিকে binaryText
ভেরিয়েবলে সংরক্ষণ করি, আমাদের এটিকে বাইনারি থেকে প্লেইন টেক্সটে রূপান্তর করতে হবে। যেহেতু আমরা জানি যে প্রতিটি অক্ষর 8 বিট নিয়ে গঠিত (মনে রাখবেন কিভাবে আমরা প্রতিটি অক্ষরকে একই দৈর্ঘ্য করতে padStart(8, "0")
ব্যবহার করেছি?), আমরা binaryText
এর প্রতিটি 8 তম অক্ষরে পুনরাবৃত্তি করতে পারি।
তারপর আমরা আমাদের পুনরাবৃত্তির উপর ভিত্তি করে binaryText
থেকে বর্তমান বাইট বের করতে .slice()
অপারেশন ব্যবহার করতে পারি। parseInt(byte, 2)
ফাংশন ব্যবহার করে বাইনারি স্ট্রিংকে একটি সংখ্যায় রূপান্তর করা যেতে পারে। তারপরে আমরা ফলাফলটি 0
(একটি নাল বাইট) কিনা তা পরীক্ষা করতে পারি - আমরা রূপান্তর বন্ধ করে ফলাফলটি জিজ্ঞাসা করি। অন্যথায়, আমরা কোন অক্ষরটি ইউনিকোড নম্বরের সাথে মিলে যায় তা খুঁজে বের করতে পারি এবং এটিকে আমাদের ফলাফলের স্ট্রিংয়ে যোগ করতে পারি।
for (let i = 0; i < binaryText.length; i += 8) { let byte = binaryText.slice(i, i + 8); if (byte.length < 8) break; // Stop if the byte is incomplete let charCode = parseInt(byte, 2); if (charCode === 0) break; // Stop if we hit a null character decodedText += String.fromCharCode(charCode); }
ডিকোড করা পাঠ্যটি তারপরে ব্যবহারকারীর কাছে নিরাপদে প্রদর্শিত হতে পারে:
document.getElementById("decodedText").textContent = decodedText;
আমি এই নিবন্ধে ব্যবহৃত সম্পূর্ণ কোডটি আমার GitHub সংগ্রহস্থলে রেখেছি; এটির সাথে খেলতে বিনা দ্বিধায়। অনেক কিছু আছে যা উন্নত করা যেতে পারে :)
স্টেগানোগ্রাফি একটি অত্যন্ত শক্তিশালী কৌশল, এবং এটি ডকুমেন্ট যাচাইকরণ, ফাঁস প্রতিরোধ, চিত্র এআই যাচাইকরণ, মিউজিক ফাইল ডিআরএম ব্যবস্থাপনা এবং আরও অনেক কিছু থেকে শুরু করে বিভিন্ন ব্যবহারের ক্ষেত্রে প্রয়োগ করা যেতে পারে। এই কৌশলটি এমনকি ভিডিও, গেমস বা এমনকি কাঁচা পাঠেও প্রয়োগ করা যেতে পারে, তাই আমি মনে করি এটির বিশাল সম্ভাবনা রয়েছে।
এনএফটি এবং ব্লকচেইনের যুগে, এটি কীভাবে এর ব্যবহারের ক্ষেত্রে খুঁজে পাবে এবং কীভাবে এই কৌশলটি বিকশিত হবে তা দেখা আরও আকর্ষণীয়।