مدتی هست که برای یک پروژه بلاکچینی درگیر base64 و انواع دیگه ی encoding ها بودم. واقعیت این همه سال من فقط از base64 استفاده میکردم و نمیدونستم که دقیقا چطوری کار میکنه! ولی برای کاری مجبور شدم خودم از اول کد اون رو بنویسم. داخل این مقاله این الگوریتم رو باهم بررسی میکنیم و اون رو پیاده سازی میکنیم.
وقتی شما قرار باشه اطلاعات رو در یک شبکه جابجا بکنید، معمولا اون رو به صورت بایت یا بیت جابجا نمیکنید. (منظورم در سطح application هست. در سطوح پایین این اتفاق خودکار میفته.) به دلیل اینکه یکسری پروتکل ها فقط text stream هستند. و یا اینکه ممکنه اون اطلاعاتی رو که به صورت بیت به بیت میفرستید درواقع دستورات کنترلی یک پروتکل باشند (مثل یک modem) و یا اینکه یکسری پروتکل ها ممکنه به اشتباه داخل اطلاعاتتون یک کاراکتر خاص تشخیص بدن و همه چیز به هم بریزه! (مثل پروتکل FTP که پایان هر خط رو تشخیص میده)
الگوریتم base64 در واقع اطلاعاتی که نیاز هست داخل شبکه جابجا بشن رو تبدیل میکنه به text. این الگوریتم اطلاعات رو تبدیل میکنه به ترکیبی از 64 تا کاراکتر که جلوتر بررسی میکنیم. پس حالا با خیال راحت و بدون درد سر میشه فایل های باینری رو با این الگوریتم داخل شبکه جابجا کرد.
همینطور که گفتم هدف اصلی این الگوریتم تبدیل هر داده ای به text هست. حالا میخوام با یک مثال عملی، طرز کارکرد این الگوریتم رو توضیح بدم. (زبانی که انتخاب کردم برای ادامه Python هست)
فرض کنید که قرار هست اطلاعات متغیر زیر رو با این الگوریتم encode کنیم.
>>> content = "Hello world"
طبیعاتا اولین قدم این هست که این که داده ی مورد نیاز برای ورودی الگوریتم base64 رو فراهم کنیم. و نوع این داده باید bytes باشه. یعنی درواقع مقدار عددی هر کاراکتر رو به دست بیاریم. پس محتوای متغیر بالارو تبدیل میکنیم به یک list از اعداد.
>>> input_content = list(content.encode('utf-8')) >>> print(input_content) [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100,]
همینطور که میبینید حالا لیستی از اعداد داریم که میتونیم روش کار کنیم.
قدم بعدی این هست تمام این اعداد رو به فرمت binary تبدیل کنیم و اون ها رو به صورت 0 و 1 بنویسیم و تبدیلشون کنیم به یک sequence از 0 ها و 1 ها.
>>> bin_data = "".join([format(i, "08b") for i in input_content]) >>> print(bin_data) '0100100001100101011011000110110001101111001000000111011101101111011100100110110001100100'
حالا اینجا میرسیم به اینکه اصلا چرا base64. چرا base32 نه، چرا base16 نه؟
درواقع دو به توان 6 میشه 64. و ما هم قرار هست اطلاعات رو تبدیل کنیم به متنی که ترکیبی از 64 کاراکتر هست. پس اینجا 64 و 6 اعداد کلیدی ما هستند. به بیان دیگه با 6 بیت میشه اعداد 0 تا 63 رو ساخت. از 000000 تا 111111
قدم بعدی این هست که این داده رو به قسمت های 6 بیتی تقسیم کنیم. قبلش باید مطمعن بشیم هر قسمت دقیقا 6 بیت خواهد داشت. یعنی قسمت آخر کمتر از 6 بیت نباشه.
مثال بالا این حالت پیش میاد و ما مجبوریم برای اینکه آخرین قسمت دقیقا 6 بیت داشته باشه، به آخر این رشته باینری 0 اضافه کنیم. انقدر 0 اضافه کنیم تا آخرین قسمت 6 بیت بشه.
ولی اگر این کار رو انجام بدیم درواقع داریم داخل اطلاعات اصلیمون دستکاری میکنیم و این اصلا خوب نیست! پس نیاز به چیزی داریم که داخل خروجی base64 نشون بدیم که چند تا 0 اضافه کردیم که موقع decode کردن الگوریتم متوجه بشه باید چند 0 از آخر حذف کنه.
برای اینکار، تعداد 0 هایی که اضافه کردیم رو میشماریم، ذخیره میکنیم و در نهایت بعد از اینکه خروجی base64 کاملا به دست اومد، به آخر خروجی به اندازه 0 هایی که اضافه کردیم کاراکتر "=" اضافه میکنیم. به این کاراکتر داخل این الگوریتم میگیم pad.
>>> data_chunks = [bin_data[i:i+6] for i in range(0, len(bin_data), 6)] >>> pads = 6 - len(data_chunks[-1]) >>> data_chunks[-1] += "0" * pads >>> print(data_chunks) ['010010', '000110', '010101', '101100', '011011', '000110', '111100', '100000', '011101', '110110', '111101', '110010', '011011', '000110', '010000']
حالا هرکدوم از این بخش های 6 بیتی درواقع قابلیت این رو دارن که تبدیل بشن به کاراکتر متنظارشون داخل جدول base64. (گفتیم خروجی base64 یک text هست که از ترکیب 64 کاراکتر تشکیل شده) پس حالا هر قسمت رو با توجه به جدول زیر تبدیل به کاراکتر میکنیم:
>>> table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" >>> output = "".join([table[int(i, 2)] for i in data_chunks]) >>> print(output) 'SGVsbG8gd29ybGQ'
و حالا فقط کافیه اگه نیازی به اضافه کردن pad هست این کار رو انجام بدیم.
>>> output += "=" * pads >>> print(output) 'SGVsbG8gd29ybGQ=='
تموم شد! این خروجی base64 ما هست.
این هم کد کامل:
content = "Hello world" input_content = list(content.encode('utf-8')) bin_data = "".join([format(i, "08b") for i in input_content]) data_chunks = [bin_data[i:i+6] for i in range(0, len(bin_data), 6)] pads = 6 - len(data_chunks[-1]) data_chunks[-1] += "0" * pads table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" output = "".join([table[int(i, 2)] for i in data_chunks]) output += "=" * pads print(output)