D ve Python şifre güvenliği

D ve Python dillerini kullanarak şifre güvenliği

Bu makalede D ve Python dilleri ile şifre güvenliği konusundan bahsedeceğiz.

Örüt ağ üzerinde kullandığımız sunucu uygulamalarında oturum açma genellikle bir kullanıcı adı ve şifre girilerek yapılıyor.

Ancak bu bilgileri sunucu üzerinde saklamak güç olabilir.

Şifreleri saklamak riskli ve güvensiz olabilir

Bu konuda basit bir yaklaşım kullanıcı isimlerini ve şifreleri veritabanı üzerinde düz metin olarak saklamaktır.

Ancak bir bilgisayar korsanı eğer veritabanını kırıp bu bilgilere erişirse her kullanıcının hesabına erişim sağlayabilir.

Daha güvenli bir yöntem ise bu bilgileri tekrar şifreye döndürülemeyecek şekilde tek taraflı bir anahtarlama mekanizması kullanmaktır.

Tek taraflı anahtarlama

Tek taraflı anahtarlamanın yabancı dildeki karşılığı olan "hashing" ufalama, parçalara ayırma gibi anlamlara geliyor.

Teknik olarak da girilen bir veriyi tek taraflı anahtarlayarak sabit bir genişlikte bit dizilimi oluşturan matematiksel bir işlev olarak düşünebiliriz.

Tek taraflı anahtarlamanın şu özelliklere sahip olması beklenir.

  • Tek taraflı anahtar oluşturmak kolay ve pratik olmalı. Diğer taraftan sadece tek taraflı anahtar kullanılarak ilk veriyi elde etmek çok zor, hatta imkansız olmalı.
  • İstenen belirli bir anahtarla eşleşen ilk girdiyi üretmek zor olmalıdır.

Özetlemek gerekirse şifrelemenin tersine tek taraflı anahtarlama, tek yönlü bir mekanizmadır.

Tek taraflı anahtarlama - Hashing

Yaygın olarak kullanılan tek taraflı anahtarlama algoritmalarından bazıları MD5 ve SHA algoritmalarıdır.

SHA-256 algoritması da sık kullanılan tek taraflı anahtarlama algoritmalarından biridir.

SHA-256 algoritması kullanarak bir tane Python örneği yazalım.

from hashlib import sha256
anahtar = sha256()
anahtar.update("çorba".encode('utf-8'))
sonuç = anahtar.hexdigest()
print(sonuç)

Çıktısı şu şekilde olacak.

51bcb4799af84421bb5b230ff2516e1ba017f6f03a2c0f1be1e46215c2aa6d9a

Şimdi aynı örneği D ile yazalım.

import std.digest.md;
import std.digest.sha;
import giriş = std.stdio;

void main()
{
    auto anahtar = makeDigest!SHA256();
    anahtar.put(cast(ubyte[])"çorba");
    auto sonuç = anahtar.finish();
    giriş.writeln(sonuç.toHexString());
}

D uygulamasını derlemek için dmd şifre.d -ofşifre komutlarını verebilirsiniz. Programı ./şifre şeklinde çalıştırdığımız zaman çıktıya bakalım.

Dikkat ederseniz farklı programlama dilleri kullanmamıza rağmen aynı çıktıyı verdi.

51BCB4799AF84421BB5B230FF2516E1BA017F6F03A2C0F1BE1E46215C2AA6D9A

Örneğin çorbalar kelimesi için tekli anahtar oluşturmayı deneyin.

Aşağıdaki çıktıyı alabildiniz mi?

19c7019f8ef668f95796221aadac2de6f5a68a262476acfe68cf97ce8aeb7fb1

Dikkat ederseniz çorba ve çorbalar kelimelerinin uzunlukları farklı idi. Ancak her ikisi de eşit uzunluklu tekli anahtar üretti.

Tekli anahtarlamanın sınırlamaları

Tekli anahtarlama oldukça güvenli gözükebilir. Ancak bir bilgisayar korsanının sunucuya sızıp şifre anahtarlarını çaldığını düşünelim.

Eğer birden fazla kullanıcı aynı şifreyi kullanmışsa oluşturulan anahtarlar da aynı olacaktır. Bu durumda korsan belirli tekniklerle anahtarları kırabilir.

Bu durumda ilk veriye kayıt edilirken rastgele bir veri ekleyebiliriz.

Bu eklediğimiz rastgele veriye yabancı dilde "salt" deniyor. Türkçe karşılığı olarak çeşni diyebiliriz.

İlk örneğimizde her iki kullanıcının şifresini çorba kabul edelim. Bu şifreler için tekli anahtar oluştururken çeşni olarak bir tanesine tuz, diğerine nane ekleyelim.

D örneğine bakalım:

import std.digest.md;

import std.digest.sha;
import giriş = std.stdio;

void main()
{
    auto anahtar = makeDigest!SHA256();
    anahtar.put(cast(ubyte[])"tuzlu");
    anahtar.put(cast(ubyte[])"çorba");
    auto sonuç = anahtar.finish();
    giriş.writeln(sonuç.toHexString());
}

Çıktısı aşağıdaki gibi olacak.

5E2A4F0ACC0AD3875BECC6FC1D5A62B0D700C692411B43A0B022A7C6AD05843A

Python örneğine de bakalım.

from hashlib import sha256
anahtar = sha256()
anahtar.update("naneliçorba".encode('utf-8'))
sonuç = anahtar.hexdigest()
print(sonuç)

15a1e1ef94f26249980d283bda8d7fcf1cebae227c91d5a6bec7ceec9c07762c

Görebileceğiniz üzere üretilen tekli anahtarlar farklı oldu.

Bu arada sonuçta yayla çorbası yapmıyoruz ;-)

Çeşni olarak tuz, nane, kekik kullanmak zorunda değiliz.

D dili unicode karakter kullanımı

D dili unicode karakterleri destekliyor.

import std.stdio;

void main()
{
    dstring japonca = "さいごの果実";
    writeln(japonca);
}

Bu uygulamayı derleyip çalıştırınca beklenildiği gibi ekrana さいごの果実 yazıyor.

Hazır Unicode karakterlerden söz açılmışken, D ile Unicode karakter kullanımının ne kadar kolay olduğunu örnekleyen ufak bir örnek yazdım.

import std.uni : unicode;
import std.random : randomSample;
import std.stdio : write, writeln;
import std.conv : to;

void main()
{
    auto unicodeKarakterler = unicode("Old_Turkic") | unicode ("Mongolian");

    dstring karakterler = to!dstring(unicodeKarakterler.byCodepoint);
    auto seçim = randomSample(karakterler, 32);

    foreach (harf; seçim)
        write(harf, " ");
    writeln();
}

Bu program Göktürk ve Moğol alfabesinden rastgele 32 harf seçiyor.

᠀ ᠈ ᠑ ᠔ ᠠ ᠰ ᡅ ᡆ ᡉ ᡗ ᡙ ᡤ ᡷ ᢅ ᢆ ᢋ ᢐ ᢔ ᢢ ᢤ ᢥ ᢪ 𐰎 𐰒 𐰗 𐰚 𐰞 𐰰 𐰷 𐰺 𐰿 𐱈

Görebileceğiniz üzere oldukça değişik çıktılar üretiyor.

᠕ ᠙ ᠤ ᠮ ᠷ ᡂ ᡍ ᡩ ᡮ ᢄ ᢎ ᢒ ᢘ ᢛ ᢜ ᢟ ᢡ ᢪ 𐰃 𐰅 𐰋 𐰖 𐰚 𐰜 𐰞 𐰦 𐰧 𐰭 𐰮 𐱈

Günümüzde MD5 algoritması güvenli kabul edilmiyor.

Google ise 2017 yılında SHA1'i kırdığını duyurdu.

D ile çeşni üretmek

D dili ile çeşni üretmek için Linux altında std.process kütüğünde tanımlı executeShell isimli bir işlevden yararlanacağız.

Bu işlev işletim sisteminde komut satırında bir komutu işletmeyi sağlıyor. Windows için de benzer komutları deneyebilirsiniz.

Bu örnekte kullandığımız komut bir dizindeki tüm dosyaların listesini veriyor. Bu komutu terminalde çalıştırdıktan sonra çıktısını alabiliyoruz.

import std.stdio;
import std.process;

void main()
{
    auto komut = "ls -l";
    auto listele = executeShell(komut);
    if (listele.status != 0)
        writeln("Kütükleri gösterme işlemi başarısız oldu");
    else
    {
        auto sonuç = listele.output;
        write(sonuç);
    }
}

Çeşni üretmek için Linux altında /dev/urandom'a bağlanıp rastgele değerler alıyoruz. Windows altında da Türkçe alfabe, sayılar ve diğer karakterlerden rastgele değerler seçiyoruz.

Çeşni üreten kütüğümüzün son hali şu şekilde oldu.

module anahtar.teklianahtar;

string çeşniÜret(uint uzunluk = 32)
{
    import std.process;
    import std.stdio;
    import std.conv;
    import std.random, std.array, std.range;

    version (Posix)
    {
        string kaçTane = to!string(uzunluk);
        auto komut = "LC_ALL=C tr -dc '[:alnum:]' < /dev/urandom | head -c" ~ kaçTane;
        auto rastgele = executeShell(komut);
        if (rastgele.status != 0)
            writeln("Rastgele dizge üretilmesi başarısız oldu");
        else
        {
            return rastgele.output;
        }
    }
    else version (Windows)
    {
        dchar[] alfabe = "abcçdefgğhıijklmnoöprsştuüvyzABCÇDEFGĞHIİJKLMNOÖÖPRSŞTUÜVYZ"d.dup;
        alfabe ~= "0123456789~!@#$%^&*()-+=?/<>|[]{}_:;.,`"d.dup;
        dchar[] sonuç = array(take(randomCover(alfabe, rndGen), uzunluk));
        return to!string(sonuç);
    }
    assert(0);
}

unittest
{
    import std.stdio;
    auto çeşni = çeşniÜret();
    writeln("Çeşni = ", çeşni);
}

void main()
{}

Çıktılara bakacak olursak, Linux ortamında üretilen değerler

lh0Vdl1VcdjvqbzDuJlmfcBniEFwPqpF

şeklinde Türkçe karakterler olmadan, Windows altında üretilen çeşni ise

T#Ö_84üFnu&y~+?`GI:^237Nşt)CbUVs

Türkçe karakterleri de içerecek şekilde oldu.

Yukarıdaki uygulamada birim testi de olduğu için komut satırından

$ dmd teklianahtar.d -w -unittest

şeklinde derlemek gerekiyor.

Daha önce öğrendiğimiz SHA256 algoritmasını kullanarak tekli anahtar üretirsek kodun son hali şu şekilde olacak.

module anahtar.teklianahtar;

string çeşniÜret(uint uzunluk = 32) @safe
{
    import std.process;
    import std.stdio;
    import std.conv;
    import std.random, std.array, std.range;

    version (Posix)
    {
        string kaçTane = to!string(uzunluk);
        auto komut = "LC_ALL=C tr -dc '[:alnum:]' < /dev/urandom | head -c" ~ kaçTane;
        auto rastgele = executeShell(komut);
        if (rastgele.status != 0)
            writeln("Rastgele dizge üretilmesi başarısız oldu");
        else
        {
            return rastgele.output;
        }
    }
    else version (Windows)
    {
        dchar[] alfabe = "abcçdefgğhıijklmnoöprsştuüvyzABCÇDEFGĞHIİJKLMNOÖÖPRSŞTUÜVYZ"d.dup;
        alfabe ~= "0123456789~!@#$%^&*()-+=?/<>|[]{}_:;.,`"d.dup;
        dchar[] sonuç = array(take(randomCover(alfabe, rndGen), uzunluk));
        return to!string(sonuç);
    }
    assert(0);
}

private bool anahtarıDoğrula(ubyte[32] anahtar, string şifre, string çeşni)
{
    import std.digest.sha;
    auto geçiciAnahtar = makeDigest!SHA256();
    geçiciAnahtar.put(cast(ubyte[])şifre);
    geçiciAnahtar.put(cast(ubyte[])çeşni);
    auto sonuç = geçiciAnahtar.finish();
    return anahtar == sonuç;
}

private ubyte[32] tekliAnahtarÜret(string şifre, string çeşni)
{
    import std.digest.sha;
    auto anahtar = makeDigest!SHA256();
    anahtar.put(cast(ubyte[])şifre);
    anahtar.put(cast(ubyte[])çeşni);
    auto sonuç = anahtar.finish();
    return sonuç;
}

unittest
{
    import std.stdio;
    import std.digest : toHexString;

    auto çeşni = çeşniÜret();
    writeln("Çeşni = ", çeşni);
    auto şifre = "abc";
    auto anahtar = tekliAnahtarÜret(şifre, çeşni);
    writeln("Anahtar = ", anahtar.toHexString());
    assert(anahtarıDoğrula(anahtar, şifre, çeşni));
    assert(!anahtarıDoğrula(anahtar, "ab", çeşni));
    auto farklıÇeşni = çeşniÜret();
    assert(!anahtarıDoğrula(anahtar, şifre, farklıÇeşni));
}

void main()
{}

Bu arada şifre bilimi açısından bu tür eklemeli algoritmaların bir açık oluşturabileceğini düşünenler de var.

Merak edenler konuyla ilgili bu makaleyi okuyabilirler.

Ama bizim oluşturduğumuz tekli anahtar, sunucu üzerinde kullanıcı şifre bilgilerini saklamak yerine büyük ölçüde güvenlik sağlıyor.

Yorumlar

yorum yaz

Yorum yaz

Henüz yorum yok.