paint-brush
Steganografi: JavaScript Kullanarak Görsellerdeki Metin Nasıl Gizlenir?ile@andriiromasiun
1,467 okumalar
1,467 okumalar

Steganografi: JavaScript Kullanarak Görsellerdeki Metin Nasıl Gizlenir?

ile Andrii Romasiun9m2024/06/26
Read on Terminal Reader

Çok uzun; Okumak

Steganografi, gizli mesajları gizli olmayan başka bir dosyada saklama yöntemidir. Kullanıcılar, içindeki gizli mesajı okumak veya bir görseldeki mesajı kendileri kodlamak için bir görsel yükleyebilir. Bu makalede, böyle bir kodlama motorunun JavaScript kullanılarak nasıl uygulanacağı açıklanmaktadır.
featured image - Steganografi: JavaScript Kullanarak Görsellerdeki Metin Nasıl Gizlenir?
Andrii Romasiun HackerNoon profile picture
0-item

Steganografi Nedir?

Bir arkadaşınıza gizli bir mesaj göndermek istediğinizi ancak kullanmak istediğiniz kanalın güvenliğinin ihlal edildiğini ve izlendiğini düşünün. Bir miktar şifreleme kullanabilirsiniz, ancak bu, konuşmalarınızı izleyen kişilerde şüphe uyandırır, dolayısıyla başka bir şey kullanmanız gerekir.

Günümüzde steganografi, gizli mesajları gizli olmayan başka bir dosyada (bir kedi resmi gibi) saklama yöntemidir, böylece o dosyayı gönderdiğinizde tespit edilmez. Steganografi, görsellerdeki metni gizlemekle sınırlı değildir ve genellikle "gizli bilgilerin gizli olmayan başka bir mesajda veya fiziksel nesnede saklanması" anlamına gelir: ses, video veya diğer metinlerdeki bazı mesajları, örneğin sütunlu aktarım kullanarak gizleyebilirsiniz.


Steganografi diğer birçok durumda da son derece yararlı olabilir; örneğin, hassas belgeleri sızıntıya karşı korumak için filigranlamaya iyi bir alternatif olabilir.


Görüntülerdeki bilgileri gizlemenin, metni dosyanın sonuna eklemekten meta verilerde gizlemeye kadar birçok yolu vardır. Bu makalede steganografinin daha gelişmiş bir yöntemini, ikili seviyeye inerek ve mesajları görüntünün kendi sınırları içinde gizleyerek ele almak istiyorum.

Steganografik Motor Oluşturmak

Kullanıcı arayüzü

Steganografi örneğim için JavaScript kullanmaya karar verdim çünkü bu, tarayıcıda çalıştırılabilen güçlü bir programlama dilidir.


Kullanıcıların bir görseli yükleyerek içindeki gizli mesajı okumasına ya da görseldeki bir mesajı kendilerinin kodlamasına olanak tanıyan basit bir arayüz geliştirdim.


 <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>


Bunu kullanmak için kullanıcılar, değiştirmek istedikleri bir görseli seçebilir ve bu görseldeki metnin bir kısmını çözmeye çalışabilir veya onu kodlayıp görseli daha sonra indirebilirler.



Görüntüleri İşleme

JavaScript'te görsellerle çalışmak için Canvas API'sini kullanabiliriz. Görüntüleri, animasyonları ve hatta oyun grafiklerini ve videolarını değiştirmek ve çizmek için birçok farklı işlev sağlar.


Canvas API öncelikle 2D grafikler için kullanılır. 3D, donanım hızlandırmalı grafiklerle çalışmak istiyorsanız WebGL API'sini kullanabilirsiniz (bu arada <canvas> öğesini de kullanır).


 const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); const image = new Image();


Görüntü dosyasını dosya sisteminden okumak ve tuval bağlamına eklemek için FileReader API'sini kullanabiliriz. Kullanıcının bilgisayarında saklanan herhangi bir dosyanın içeriğini özel bir kütüphaneye ihtiyaç duymadan kolayca okumamızı sağlar.

 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]); }


Bir dosyayı okur ve o dosyadaki görüntüyü önceden tanımladığımız 2B tuval bağlamımıza çizer, ardından o görüntünün içine bir miktar metin kodlayabiliriz veya görüntüdeki metni okumayı deneyebiliriz.

Görsellerdeki Metni Gizleme

Görüntüler piksellerden oluşur ve her piksel kendi renkleri hakkında bilgi içerir. Örneğin, bir görüntü RGBA modeli kullanılarak kodlanırsa her piksel, ne kadar kırmızı, yeşil, mavi ve alfayı (opaklığı) temsil ettiğine ilişkin 4 baytlık bilgi içerir.


Bir görüntüdeki bazı metinleri kodlamak için bu kanallardan birini (örneğin alfa kanalı) kullanabiliriz. Bu bilgi ikili sistemde (01001100 gibi) temsil edildiğinden, son biti ihtiyacımız olana çevirebiliriz. Buna En Az Önemli Bit (LSB) adı veriliyor ve bunun değiştirilmesi görüntünün kendisinde çok az değişikliğe neden oluyor ve onu bir insandan ayırt edilemez hale getiriyor.


Şimdi "Merhaba" gibi bir metnimiz olduğunu ve bunu bir görüntüye kodlamak istediğimizi hayal edin. Bunu yapacak algoritma şu şekilde olacaktır:


  1. "Merhaba" metnini ikiliye dönüştürün.


  2. Görüntü verilerinin baytlarını yineleyin ve bu baytların LSB'sini ikili metinden bir bit ile değiştirin (her piksel, renklerin her biri için 4 bayt veri içerir; benim örneğimde görüntünün opaklık kanalını değiştirmek istiyorum) , bu yüzden her 4 baytta bir yineleme yapardım).


  3. Mesajın sonuna bir boş bayt ekleyin, böylece kod çözerken ne zaman duracağımızı bilebilelim.


  4. Değiştirilen görüntü baytlarını görüntünün kendisine uygulayın.


Öncelikle kodlamak istediğimiz metni kullanıcıdan alıp üzerinde bazı temel doğrulamalar yapmamız gerekiyor.

 const text = document.getElementById("text").value; if (!text) { alert("Please enter some text to encode."); return; }


Daha sonra metni ikiliye dönüştürmemiz ve bu metni kodlayacağımız görüntünün bir tuvalini oluşturmamız gerekiyor.

 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; }


Bunu yapmak için, karakterlerin her biri üzerinde yinelemeler yapabilir ve charCodeAt işlevini kullanarak bir Unicode dizini elde edebiliriz. Bu Unicode daha sonra ikili koda dönüştürülür ve diğer karakterlerle aynı uzunlukta olacak şekilde doldurulur.


Örneğin "H" harfi Unicode'da 72 olarak temsil edilir; daha sonra bu sayıyı ikili sayıya (1001000) dönüştürürüz ve tüm harflerin aynı uzunlukta (8 bit) olmasını sağlamak için başına 0 ekleriz (01001000).


Daha sonra, mesajın şifresini çözdüğümüzde, gerçek metin ile görüntünün rastgele piksel verilerini ayırt edebildiğimizden emin olmak için mesajın sonuna bir boş bayt eklememiz gerekir.

 binaryText += "00000000";


Ardından, görüntünün taşmaması için mesajımızı kodlamak için yeterli piksele sahip olduğundan emin olmak için bazı temel doğrulama yapmamız gerekir.

 if (binaryText.length > data.length / 4) { alert("Text is too long to encode in this image."); return; }


Daha sonra en ilginç kısım geliyor; mesajın kodlanması. Daha önce tanımladığımız veri dizisi, görüntüdeki her piksel için RGBA değerleri formundaki piksel bilgilerini içerir. Dolayısıyla, eğer görüntü RGBA ile kodlanmışsa, içindeki her piksel, veri dizisinin 4 değeriyle temsil edilir; her değer o pikselin ne kadar kırmızı, yeşil ve maviye sahip olduğunu temsil eder.

 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();


Yukarıdaki kodda ikili kodlanmış metnimizi yineliyoruz. data[i * 4] değiştirmemiz gereken bir bayt bulur ve yalnızca belirli bir kanalın baytlarını değiştirmek istediğimizden, ona erişmek için i değişkenini 4 ile çarparız.


data[i * 4] & 0b11111110 işlemi en az anlamlı biti 0 olarak ayarlar. Örneğin, data[i * 4] ikili olarak 10101101 ise, 10101101 & 11111110 işlemi 10101100 ile sonuçlanır. Bu, LSB üzerinde herhangi bir değişiklik yapmadan önce LSB'nin 0 ayarlanmasını sağlar.


parseInt(binaryText[i]) ikili kodlanmış dizeden geçerli bir bittir; ya 1 ya da 0 . Daha sonra bit düzeyinde VEYA ( | ) işlemini kullanarak bu biti LSB'ye ayarlayabiliriz. Örneğin, bitsel OR'nin sol kısmı 10101100 ise ve binaryText[i] 1 ise, 10101100 | 00000001 10101101 ile sonuçlanacaktır. Geçerli bit 0 olsaydı, OR 10101100 ile sonuçlanırdı. Bu yüzden ilk etapta LSB'yi silmek zorunda kaldık.


Mesaj kodlandıktan sonra onu geçerli tuvale yerleştirebilir ve canvas.toDataURL yöntemini kullanarak HTML'de işleyebiliriz.



Resimlerdeki Gizli Mesajların Kodunu Çözme

Bir görüntünün kodunu çözme işlemi aslında kodlamadan çok daha basittir. Yalnızca alfa kanalını kodladığımızı zaten bildiğimiz için, her 4'üncü baytı tekrarlayabilir, son biti okuyabilir, onu son dizemizde birleştirebilir ve bu verileri ikiliden bir Unicode dizeye dönüştürebiliriz.


Öncelikle değişkenleri başlatmamız gerekiyor. imgData zaten görüntü bilgileriyle doldurulmuş olduğundan (dosya sisteminden bir dosyayı her okuduğumuzda ctx.drawImage çağırırız), onu basitçe veri değişkenine çıkartabiliriz.


 const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imgData.data; let binaryText = ""; let decodedText = "";


Daha sonra görüntünün her 4 baytını yinelememiz, son biti okumamız ve bunu binaryText değişkenine birleştirmemiz gerekir.

 for (let i = 0; i < data.length; i += 4) { binaryText += (data[i] & 1).toString(); }


data[i] kodlanmış bayttır ve LSB'yi çıkarmak için bit düzeyinde AND ( & ) operatörünü kullanabiliriz. İki değer alır ve karşılık gelen her bit çifti üzerinde bir VE işlemi gerçekleştirir. data[i] 1 ile karşılaştırarak temel olarak piksel bilgisinden en az anlamlı biti izole ediyoruz ve eğer LSB 1 ise böyle bir işlemin sonucu 1 olur. LSB 0 ise sonuç da 0 olacaktır.


Tüm LSB'leri okuyup bunları binaryText değişkeninde sakladıktan sonra, onu ikili metinden düz metne dönüştürmemiz gerekir. Her karakterin 8 bitten oluştuğunu bildiğimiz için (her karakteri aynı uzunlukta yapmak için padStart(8, "0") nasıl kullandığımızı hatırlıyor musunuz?), binaryText her 8'inci karakterini yineleyebiliriz.


Daha sonra yinelememize dayalı olarak mevcut baytı binaryText çıkarmak için .slice() işlemini kullanabiliriz. İkili dize parseInt(byte, 2) işlevi kullanılarak bir sayıya dönüştürülebilir. Daha sonra sonucun 0 (boş bayt) olup olmadığını kontrol edebiliriz - dönüşümü durdururuz ve sonucu sorgularız. Aksi halde Unicode numarasına hangi karakterin karşılık geldiğini bulup sonuç stringimize ekleyebiliriz.


 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); }


Kodu çözülen metin daha sonra kullanıcıya güvenli bir şekilde görüntülenebilir:

 document.getElementById("decodedText").textContent = decodedText; 



Bu makalede kullanılan kodun tamamını GitHub depomda bıraktım; onunla oynamaktan çekinmeyin. Geliştirilebilecek çok şey var :)

Son düşünceler

Steganografi çok güçlü bir tekniktir ve belge doğrulama, sızıntı önleme, görüntü yapay zeka doğrulama, müzik dosyası DRM yönetimi ve çok daha fazlasından başlayarak birçok farklı kullanım senaryosuna uygulanabilir. Bu teknik videolara, oyunlara ve hatta ham metinlere bile uygulanabilir, dolayısıyla büyük bir potansiyele sahip olduğunu düşünüyorum.

NFT'ler ve blok zincirler çağında, kullanım durumlarını nasıl bulacağını ve bu tekniğin nasıl gelişeceğini görmek daha da ilginç.