C ile Dosya Şifreleyici Yapımı

Elektronik cihazlarda bulunan tüm veriler baytlardan oluşur. Bayt dediğimiz şey aslında cihaz belleğinde yer kaplayan 0 - 255 arası sayılardır. Bu yazıda bayt kavramını detaylı olarak açıklayarak basit veri şifreleme mantığını anlatmaya çalışacağım.

Taban Aritmetiği

Günlük hayatta kullandığımız sayılar decimal (onlu) yani on tabanındaki sayılardır. On tabanında kullanılan rakamlar 0,1,2,3,4,5,6,7,8 ve 9 dur. Onluk tabanda sayılarla işlem yaparken 10 sayısını rakam olarak kullanamayız. Kullanabileceğimiz en büyük rakam 9 (dokuz) dur.

Binary adı verilen iki tabanında sayılar olarak adlandırılan sayı sistemi yalnızca 0 (sıfır) ve 1 (bir) rakamlarından oluşur. Bu tabanda kullanabileceğimiz rakamlar ise yalnızca 0 (sıfır) ve 1 (bir) dir. Bu sayı tabanında ise 2 sayısını rakam olarak kullanamayız. Kullanabileceğimiz en büyük rakam 1 (bir) dir.

Elektronik cihazlar da işte bu binary adı verilen iki tabanındaki sayı sistemiyle çalışır. Ondalık sistemde "rakam" olarak adlandırdığımız sayılara bu sistemde bit (binary digit) adı verilir. Yani iki tabanında 10101101 sayısındaki her 0 ve 1 rakamına bit denir.

Bayt Kavramı

İki tabanındaki 8 basamaklı sayılara bayt denir. Bitlerden (binary digit) oluşan 1000 0000 sayısı tam olarak 1 bayt uzunluğundadır. 16 basamaklı 1010 1101 1111 1011 sayısı ise 2 bayt uzunluğundadır. 32 bitten oluşan (yani 32 basamaklı) iki tabanında herhangi bir sayı ise 4 bayt uzunluğunda olur.

On tabanında basamak değerleri 1, 10, 100, 1000, 10.000, 100.000... şeklinde devam ederken iki tabanında basamak değerleri 1, 2, 4, 8, 16, 32, 64, 128... şeklinde devam eder.

^ işareti kuvveti, üssü anlamında kullanılmıştır. Örneğin 5 ^ 3 = 125 ( 5 * 5 * 5 )

On tabanında basamakların değerleri:

1: 10 ^ 0 = 1
2: 10 ^ 1 = 10
3: 10 ^ 2 = 100
4: 10 ^ 3 = 1.000
5: 10 ^ 4 = 10.000
6: 10 ^ 5 = 100.000

1245 sayısının değeri bulunurken her rakam kendi basamağının değeri ile çarpılır, bu çarpımların toplamı sayının kendisine eşittir.

(1 * 1000) + (2 * 100) + (4 * 10)  + (5 * 1)
= 1000 + 200 + 40 + 5
= 1245

İki tabanında da aynı işlemi uygulayarak on tabanındaki karşılığını bulabiliriz.

İki tabanındaki basamak değerleri:

1: 2 ^ 0 = 1
2: 2 ^ 1 = 2
3: 2 ^ 2 = 4
4: 2 ^ 3 = 8
5: 2 ^ 4 = 16
6: 2 ^ 5 = 32
7: 2 ^ 6 = 64
8: 2 + 7 = 128

1101 sayısının on tabanındaki karşılığını bulmak için her rakamı (bit) basamak değeri ile çarpıp, bu çarpımları toplamalıyız.

(1 * 4) + (1 * 3) + (0 * 2) + (1 * 1)
= 4 + 3 + 0 + 1
= 8

Aynı işlemi 1111 1111 sayısı için yapalım. Bu sayı 1 baytın alabileceği en büyük değerdir. Alabileceği en küçük değer ise 0 (sıfır) dır.

(1 * 128) + (1 * 64) (1 * 32) (1 * 16) (1 * 8) (1 * 4) (1 * 2) (1 * 1)
= 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1
= 255

Yani bir baytın alabileceği en maksimum değer 255 tir. Bir baytın değeri hiç bir zaman 255 değerinden fazla olamaz. Örneğin 256 sayısına bakacak olursak ikilik tabanda (1 0000 0000) şeklinde gösterilir. Fakat bu sayı 9 bitten oluşmaktadır. Bir byte ise yalnızca 8 bitten oluşur.

Bitler Üzerinde Mantıksal İşlemler

Mantıksal VE, VEYA, YADA, DEĞİL gibi işlemlerde verilen ifadelerin DOĞRU veya YANLIŞ olma durumlarına göre işlemin sonucu DOĞRU veya YANLIŞ olur. Mantıksal ifadelerdeki bu doğru veya yanlış olma durumu YANLIŞ için 0 (sıfır) ve DOĞRU için 1 (bir) rakamları verilerek sembolize edilir.

İki tabanında bir bit (rakam) yalnızca 1 veya 0 değerini alabileceği gibi mantıksal işlemlerde ifadenin değeri veya işlemin sonucu yine yalnızca 1 veya 0 (doğru veya yanlış) olabilir. Bilgisayarların mantıksal işlemcileri bu mantık işlemlerini gerçekleştirerek programların istenen biçimde çalışmasını sağlar.

VE (and)

Mantıksal VE işleminde verilen her iki değer doğru ise sonuç doğru, değerlerden en az biri yanlış ise sonuç yanlış olur.

1 VE 1 = 1
1 VE 0 = 0
0 VE 1 = 0
0 VE 0 = 0

VEYA (or)

Mantıksal VEYA işleminde verilen değerlerden en az biri doğru ise sonuç doğru, verilen iki değer de yanlış ise sonuç yanlış olur.

1 VEYA 1 = 1
1 VEYA 0 = 1
0 VEYA 1 = 1
0 VEYA 0 = 0

DEĞİL

Mantıksal değil işlemi verilen değeri tersine çevirir. Yani değer doğru ise yanlış, yanlış ise doğru olur.

1 DEĞİL = 0
0 DEĞİL = 1

YADA (xor)

Mantıksal YADA işleminde verilen değerlerden yalnızca birinin doğru olması durumunda sonuç doğru aksi takdirde yanlış olur.

1 YADA 1 = 0
1 YADA 0 = 1
0 YADA 1 = 1
0 YADA 0 = 0

Yada işlemi verileri şifrelemek için de kullanılır. Şifre olarak belirtilen verinin her baytı ile şifrelenecek verinin her baytına ayrı ayrı YADA işlemi uygulanır ve sonuç olarak bulunan değer şifrelenmiş veri olarak kaydedilir.

Bitler üzerinde ayrı ayrı mantıksal işlemler uygulanabildiği gibi baytlar (8 bit) üzerinde de mantıksal işlemler uygulanabilir. Bu işlemin sonucunda yeni bir bayt verisi elde edilir.


(a) VE (b) = (c)

1010 0101 (a) VE
0101 1010 (b)
=
0000 0000 (c) olur;

1 VE 0 = 0 olduğundan sonuç her bit için 0 (sıfır) olur.

(a) VEYA (b) = (c)

1010 1010 (a) VEYA
0101 0101 (b)
1111 1111 (c) olur;

1 VEYA 0 = 1 olduğundan sonuç her bit için 1 (bir) olur.


(a) YADA (b) = (c)

1010 1001 (a) YADA
1011 0100 (b)
=
0001 1101 (c) olur.


Yada işleminde VE, VEYA işlemlerinden farklı olarak;
(a) YADA (b) = (c) olduğu gibi aynı zamanda (c) YADA (b) işlemi ile (a) değeri bulunabilir.

0001 1101 (c) YADA
1011 0100 (b)
=
1010 1001 (a) 

YADA işleminin bu özelliğinden yola çıkarak (b) değerinin şifre olduğunu varsayarsak;
* ilk yada işlemi (a) değerini (b) değeri ile şifreleyerek (c) değerini üretir.
* Buradaki (c) değeri şifrelenmiş veridir.
* Şifrelenmiş veri (c) tekrar şifre (b) ile yada işlemine tabi tutulunca asıl veri (a) bulunur.

Elektronik cihazda tüm veriler byte olarak saklanır ve bu byte (8 bitten oluşan sayı) değerleri veri türünden bağımsızdır. Yani her metin dosyası, word belgesi, müzik veya video dosyası baytlardan oluşan bir küme olarak saklanır.

Yada işlemini baytlar üzerinde uygulayabildiğimize göre istediğimiz herhangi bir dosyayı istediğimiz şifreyle yada işlemine tabi tutarak şifreleyebiliriz. Ve bu dosya yalnızca bu şifre ile tekrar yada işlemine tabi tutularak açılabilir.

Baytları 1111 0010 şeklinde gösterebildiğimiz gibi 0 - 255 olarak ondalık biçiminde de gösterebiliriz.





Yukarıdaki resimde Dec (decimal) sütünunda karakterlerin ondalık sayı türünden değerleri belirtilmiştir. 

İçeriği: "merhaba" olan bir metin belgesinin bayt değerleri şu şekilde olacaktır:
77 (m), 101 (e), 114 (r), 104 (h), 97 (a), 98 (b), 97 (a)

Bu veriyi yalnızca "y" harfinden oluşan bir şifreyle şifrelemek istersek her baytı "y" karakterinin bayt değeri olan 121 ile ayrı ayrı yada işlemine tabi tutmamız gerekir.

77 (m) yada 121 (y)
101 (e) yada 121 (y)
114 (r) yada 121 (y)
...

C programlama dilinde ve birçok benzer dilde YADA mantıksal işlemi ^ operatörü ile sağlanır. Biz anlamak için kendimiz elle işlemi yazmaya çalışalım.

77 ^ 121 = ?

Öncelikle bu sayıları bit (1 veya 0) şeklinde yazmamız gerekecek.

10 tabanındaki bir sayı 2 tabanına çevrilirken bölüm 2 den büyük olduğu sürece 2 ile bölünür. En son bölüm ile geriye doğru kalanlar ardarda yazılarak 2 tabanındaki gösterim bulunur.

77 / 2 = 38 (kalan 1)
38 / 2 = 19 (kalan 0)
19 / 2 = 9 (kalan 1)
9 / 2 = 4 (kalan 1)
4 / 2 = 2 (kalan 0)
2 / 2 = 1 (kalan 0)


Yukarıdaki işlemde son bölüm olan 1 (bir) ve kalan 0 (sıfır) dan başlayarak yukarı doğru kalanlar yazılılır. Bulunan sayı 77 sayısının 2 tabanında gösterimidir.

1001101 = 0100 1101 (77)

121 / 2 = 60 (kalan 1)
60 / 2 = 30 (kalan 0)
30 / 2 = 15 (kalan 0)
15 / 2 = 7 (kalan 1)
7 / 2 = 3 (kalan 1)
3 / 2 = 1 (kalan 1)

Yukarıdaki işlemde de son bölüm olan 1 ve kalan 1 den başlayarak yukarı doğru kalanlar sırayla yazıldığında 121 sayısının 2 tabanındaki gösterimi bulunur.

1111001 = 0111 1001 (121)

Şimdi bu iki bayt verisine YADA işlemi uygulayalım.

0100 1101 (77) YADA
0111 1001 (121)
=
0011 0100 değerini buluruz.

Bu sayıyı tekrar ondalık tabana çevirmek istersek 0 olan basamakların değeri yine sıfır olduğu için yalnızca 1 olan basamakların değerlerini toplamak yeterli olacaktır. 3., 5. ve 6. basamaklardaki değerlerin 1 olduğunu görüyoruz. Basamak değerini 2 ^ (basamak - 1) formülü ile bulabiliriz.

(1 * 2 ^ 5) + (1 * 2 ^ 4) + (1 * 2 ^ 2)
= 32 + 16 + 4
= 52

Demekki 77 (m) baytını 121 (y) baytı ile YADA işlemine tabi tutunca 52 bayt değerini elde ediyoruz. Buradaki 52 değeri aslında şifrelenmiş olan veridir. Bunu tekrar 121 (y) şifre değeri ile yada işlemine tabi tutarsak asıl değeri buluruz.

0011 0100 (52) YADA
0111 1001 (121)
=
0100 1101 (77) olur.

Şifrelenmiş 52 değeri hiç bir anlam ifade etmezken ancak şifre olan 121 değeri ile tekrar yada işlemine tabi tutulduğu zaman gerçek değeri olan 77 değeri bulunabiliyor.

Bu örneklerde şifremiz yalnızca tek karakterden "y" (121) oluşuyordu. Bu güvenli bir şifre sayılmaz. Çünkü şifreyi aynı yöntemle çözmeyi deneyen biri 0 dan 255 e kadar tüm olası bayt değerlerini deneyerek asıl verinin "merhaba" karakteri olduğunu öğrenebilir.

Şifre ne kadar uzun olursa şifrelemenin güvenliği o kadar fazla olur. Şifremiz "ywxfe" olsaydı bu kez "merhaba" verisinin her karakteri ile "ywxfe" şifresinin her karakterine sırayla yada işlemi uygulanacaktı.

İlk karakter için yada sonucu bu işlemin sonucu olacaktı:

((((('m' ^ 'y') ^ 'w') ^ 'x') ^ 'f') ^ 'e')

Bu gösterimde 'm' şeklinde tırnak içinde belirtilen değer m harfinin ASCII karşılığını yani 77 sayısını belirtir. ^ işlemi ise mantıksal YADA işlemidir.

Şifreleme işlemini dosyalar için yapan C kodu aşağıdadır:

Kullanım:
haci encrypt şifre dosya [kaydedilecek dosya]




main.c >> haci.exe

/*
** Programmer: Muhammed Yunus Akdağ
** Build: mingw32
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>

int main(int argc, char *argv[])
{
    // Allow Turkish password characters
    setlocale(LC_ALL, "tr_TR");

    // Check parameter count is valid
    if (argc < 4 || argc > 5)
    {
        printf("Haci Encryptor and Decryptor [Version 1.0]\n\n");
        printf("This application encrypts or decrpyts given single file with given password.\n\n");
        printf("Usage:\n");
        printf("  haci <encrypt>/<e> <password> <input-file> [out-file]\n");
        printf("  haci <decrypt>/<d> <password> <input-file> [out-file]\n\n");
        printf("Examples:\n");
        printf("  haci encrypt 1234 normal.txt\n");
        printf("  haci encrypt 1234 normal.txt secret.txt\n\n");
        printf("  haci decrypt 1234 secret.txt\n");
        printf("  haci decrypt 1234 secret.txt normal.txt\n\n");

        return -1;
    }

    char *mode = argv[1];   // encrypt|decrypt
    char *pass = argv[2];   // password
    char *file = argv[3];   // file name
    char *outFile;          // output file name

    // Allow short mode names
    if (strcmp(mode, "e") == 0) mode = "encrypt";
    if (strcmp(mode, "d") == 0) mode = "decrypt";

    // If output name gived in argument list
    if (argc == 5)
    {
        // Allocate memory for output file name
        outFile = calloc(strlen(argv[4]), sizeof(char));

        // set output file name as given in argument list
        strcpy(outFile, argv[4]);
    }
    else
    {
        // Allocate memory for output file name
        outFile = calloc(strlen(file) + strlen(mode) + 4, sizeof(char));

        // Set first as null character for empty string
        outFile[0] = '\0';

        // set output file name as filename.encrypted or filename.decrypted
        strcat(outFile, file);
        strcat(outFile, ".");
        strcat(outFile, mode);
        strcat(outFile, "ed");
    }


    // check mode is valid

    if (strcmp(mode, "encrypt") != 0 && strcmp(mode, "decrypt") != 0)
    {
        printf("Unknown command!\nPlease use encrypt/decrypt or e/d!\n");

        return -1;
    }

    // check password is valid

    if (strlen(pass) < 4 || strlen(pass) > 16)
    {
        printf("Password length must be greater than 4 and less than 16 characters.");

        return -1;
    }

    // open file in mode read-binary

    FILE *inputFile = fopen(file, "rb");

    if (inputFile == NULL)
    {
        perror("FILE_ERROR");

        printf("File: %s\n", file);

        return -1;
    }

    char encryptionMark[] = "ENCRYPTED";

    // read file header as size of encryption mark
    char buff[sizeof(encryptionMark)];
    fread(buff, sizeof(unsigned char), sizeof(buff), inputFile);

    // check if header is encryption mark
    unsigned char isMarked = strcmp(buff, encryptionMark) == 0;

    // check file is already encrypted

    if (strcmp(mode, "encrypt") == 0)
    {
        if (isMarked)
        {
            printf("This is already an encrypted file.\nDo you want to re-encrypt it? [y/n]\n");

            if (getchar() != 'y')
            {
                return 0;
            }
        }
        else
        {
            // otherwise seek to first character header is not useless
            fseek(inputFile, 0, 0);
        }
    }

    // check file is encrypted

    if (strcmp(mode, "decrypt") == 0)
    {
        if (!isMarked)
        {
            printf("This file was not encrypted with this program!\nStill do you want to try decrypt it. [y/n]\n");

            if (getchar() != 'y')
            {
                return 0;
            }
        }
    }

    // open output file with mode write-brinary

    FILE *outputFile = fopen(outFile, "wb");

    if (outputFile == NULL)
    {
        perror("FILE_ERROR");

        printf("File: %s\n", outFile);

        return -1;
    }

    // write encryption mark to detect encrypted and non-encrypted files

    if (strcmp(mode, "encrypt") == 0)
    {
        fwrite(encryptionMark, sizeof(encryptionMark), sizeof(unsigned char), outputFile);
    }

    // buffer size 4 kb

    unsigned char bytes[4096];

    unsigned long total_bytes = 0;

    int length;

    do
    {
        // read from input file

        length = fread(bytes, sizeof(unsigned char), sizeof(bytes), inputFile);

        // for every file bytes

        for (int i = 0; i < length; i++)
        {
            // for every password bytes

            for (unsigned int j = 0; j < sizeof(pass); j++)
            {
                // xor password byte and file byte in buffer

                bytes[i] ^= pass[j];
            }
        }

        // write encrypted or decrypted data to output file

        fwrite(bytes, length, sizeof(unsigned char), outputFile);

        total_bytes += length;

    } while (length > 0);

    printf("%lu bytes processed successfully.\n", total_bytes);

    fclose(inputFile);
    fclose(outputFile);

    return 0;
}





Bu blogdaki popüler yayınlar

Almanca - Nomen-Verb-Verbindungen

Web Tasarımı Kodlamak (Front-End)