कल्पना करें कि आप किसी मित्र को गुप्त संदेश भेजना चाहते हैं, लेकिन जिस चैनल का आप उपयोग करना चाहते हैं, वह समझौता किया हुआ है और उस पर निगरानी रखी जा रही है। आप कुछ एन्क्रिप्शन का उपयोग कर सकते हैं, लेकिन इससे आपकी बातचीत पर नज़र रखने वाले लोगों का संदेह बढ़ जाएगा, इसलिए आपको कुछ और उपयोग करना होगा।
आजकल, स्टेगनोग्राफी गुप्त संदेशों को किसी अन्य, गैर-गुप्त फ़ाइल (जैसे कि बिल्ली की तस्वीर) में छिपाने की एक विधि है, ताकि यदि आप उस फ़ाइल को भेजते हैं, तो उसका पता न चले। स्टेगनोग्राफी केवल छवियों में पाठ छिपाने तक सीमित नहीं है और आम तौर पर इसका अर्थ है "किसी अन्य गैर-गुप्त संदेश या भौतिक वस्तु में गुप्त जानकारी छिपाना": आप ऑडियो, वीडियो या अन्य टेक्स्ट में कुछ संदेशों को छिपा सकते हैं, उदाहरण के लिए, कॉलमर ट्रांसपोज़िशन का उपयोग करके।
स्टेग्नोग्राफ़ी कई अन्य मामलों में भी अत्यंत उपयोगी हो सकती है, उदाहरण के लिए, यह संवेदनशील दस्तावेजों को लीक होने से बचाने के लिए वॉटरमार्किंग का एक अच्छा विकल्प हो सकता है।
छवियों में जानकारी छिपाने के कई तरीके हैं, फ़ाइल के अंत में टेक्स्ट जोड़ने से लेकर मेटाडेटा में इसे छिपाने तक। इस लेख में, मैं स्टेगनोग्राफी की एक और अधिक उन्नत विधि को कवर करना चाहता हूँ, जो बाइनरी स्तर तक जाती है और छवि की सीमाओं के भीतर संदेशों को छिपाती है।
स्टेग्नोग्राफ़ी के अपने उदाहरण के लिए, मैंने जावास्क्रिप्ट का उपयोग करने का निर्णय लिया क्योंकि यह एक शक्तिशाली प्रोग्रामिंग भाषा है जिसे ब्राउज़र में निष्पादित किया जा सकता है।
मैंने एक सरल इंटरफ़ेस बनाया है जो उपयोगकर्ताओं को एक छवि अपलोड करके उसमें छिपे संदेश को पढ़ने, या स्वयं एक छवि में संदेश को एनकोड करने की अनुमति देता है।
<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>
इसका उपयोग करने के लिए, उपयोगकर्ता बस उस छवि का चयन कर सकते हैं जिसे वे संशोधित करना चाहते हैं और या तो उसमें से कुछ पाठ को डिकोड करने का प्रयास कर सकते हैं, या उसे एनकोड कर सकते हैं और बाद में छवि को डाउनलोड कर सकते हैं।
जावास्क्रिप्ट में छवियों के साथ काम करने के लिए, हम कैनवस एपीआई का उपयोग कर सकते हैं। यह छवियों, एनिमेशन या यहां तक कि गेम ग्राफिक्स और वीडियो को हेरफेर करने और खींचने के लिए कई अलग-अलग फ़ंक्शन प्रदान करता है।
कैनवास एपीआई मुख्य रूप से 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) कहा जाता है, और इसे बदलने से छवि में बहुत कम बदलाव होता है, जिससे यह किसी इंसान से अलग नहीं हो पाती।
अब, कल्पना करें कि हमारे पास "हैलो" जैसा टेक्स्ट है और हम इसे एक इमेज में एनकोड करना चाहते हैं। ऐसा करने के लिए एल्गोरिदम होगा
"हैलो" पाठ को बाइनरी में परिवर्तित करें।
छवि डेटा के बाइट्स के माध्यम से पुनरावृति करें, और उन बाइट्स के LSB को बाइनरी टेक्स्ट से बिट के साथ बदलें (प्रत्येक पिक्सेल में प्रत्येक रंग के लिए 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) में बदल देते हैं, और शुरुआत में 0 जोड़ देते हैं (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
है। फिर हम इस बिट को बिटवाइज़ OR ( |
) ऑपरेशन का उपयोग करके LSB पर सेट कर सकते हैं। उदाहरण के लिए, यदि बिटवाइज़ OR का बायाँ भाग 10101100
है और binaryText[i]
1
है, तो 10101100 | 00000001
परिणाम 10101101
होगा। यदि वर्तमान बिट 0
था, तो OR का परिणाम 10101100
होगा। यही कारण है कि हमें सबसे पहले LSB को हटाना पड़ा।
एक बार संदेश एनकोड हो जाने के बाद, हम इसे वर्तमान कैनवास में रख सकते हैं और canvas.toDataURL
विधि का उपयोग करके इसे HTML में प्रस्तुत कर सकते हैं।
किसी छवि को डिकोड करने की प्रक्रिया वास्तव में एनकोडिंग की तुलना में बहुत सरल है। चूँकि हम पहले से ही जानते हैं कि हमने केवल अल्फा चैनल को एनकोड किया है, इसलिए हम बस हर चौथे बाइट पर पुनरावृति कर सकते हैं, अंतिम बिट को पढ़ सकते हैं, इसे हमारे अंतिम स्ट्रिंग में जोड़ सकते हैं, और इस डेटा को बाइनरी से यूनिकोड स्ट्रिंग में बदल सकते हैं।
सबसे पहले, हमें वेरिएबल को इनिशियलाइज़ करना होगा। चूँकि imgData
पहले से ही इमेज की जानकारी भरी हुई है (हम हर बार जब हम फ़ाइल सिस्टम से कोई फ़ाइल पढ़ते हैं तो ctx.drawImage
कॉल करते हैं), हम इसे आसानी से डेटा वेरिएबल में निकाल सकते हैं।
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imgData.data; let binaryText = ""; let decodedText = "";
फिर हमें छवि के हर चौथे बाइट पर पुनरावृति करनी होगी, अंतिम बिट को पढ़ना होगा, और इसे binaryText
वेरिएबल से जोड़ना होगा।
for (let i = 0; i < data.length; i += 4) { binaryText += (data[i] & 1).toString(); }
data[i]
एन्कोडेड बाइट है, और LSB को निकालने के लिए, हम बिटवाइज़ AND ( &
) ऑपरेटर का उपयोग कर सकते हैं। यह दो मान लेता है और संबंधित बिट्स के प्रत्येक जोड़े पर एक AND ऑपरेशन करता है। data[i]
तुलना 1
से करके, हम मूल रूप से पिक्सेल जानकारी से सबसे कम महत्वपूर्ण बिट को अलग करते हैं, और यदि LSB 1
है, तो इस तरह के ऑपरेशन का परिणाम 1
है। यदि LSB 0
है, तो परिणाम भी 0
होगा।
एक बार जब हम सभी LSBs को पढ़ लेते हैं और उन्हें 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 रिपॉजिटरी में छोड़ दिया है; आप इसके साथ खेलने के लिए स्वतंत्र हैं। ऐसी बहुत सी चीजें हैं जिन्हें सुधारा जा सकता है :)
स्टेग्नोग्राफ़ी एक बहुत ही शक्तिशाली तकनीक है, और इसे दस्तावेज़ सत्यापन, लीक रोकथाम, छवि AI सत्यापन, संगीत फ़ाइल DRM प्रबंधन, और कई अन्य से शुरू करके बहुत सारे अलग-अलग उपयोग मामलों में लागू किया जा सकता है। इस तकनीक को वीडियो, गेम या यहां तक कि कच्चे पाठ पर भी लागू किया जा सकता है, इसलिए मुझे लगता है कि इसमें बहुत संभावनाएं हैं।
एनएफटी और ब्लॉकचेन के युग में, यह देखना और भी दिलचस्प है कि इसके उपयोग के मामले कैसे मिलेंगे और यह तकनीक कैसे विकसित होगी।