= "abcdefghijklmnopqrstuvwxyz"
alphabet_lower = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" alphabet_upper
التشفير
التعمية أو التشفير المُلغَّز أو الإلغاز (بالإنجليزية: Cryptography) هو فرع من علم التعمية يهتم بممارسة بعض التقنيات لتأمين عملية التواصل بوجود أشخاص أخرين والذين يسمون أعداء (adversaries). بصوره عامة، تهتم التعمية بإنشاء الأنظمة التي تمنع الأعداء أو العامة من قراءة الرسائل الخاصة. أي بوسائل تحويل البيانات (مثل الكتابة) من شكلها الطبيعي المفهوم لأي شخص إلى شكل غير مفهوم بحيث يتعذّر على من لا يملك معرفة سرية محددة معرفة فحواها.
كان غايوس يوليوس قيصر (44 ق.م) يواجه خطر تسرب المعلومات في رسائله العسكرية في حال وقعت رسائله في أيدي العدو، فابتكر وسيلة بسيطة لإخفاء مضمونها: كان يُبدل كل حرف في النص بحرف آخر يليه بعدد معين من الخانات هذه الطريقة أصبحت تُعرف اليوم بـ شيفرة قيصر (Caesar Cipher).
فإذا كان مقدار الإزاحة 3 مثلاً يصير:
- حرف
a
يصيرd
- حرف
b
يصيرe
- حرف
c
يصيرf
وأما الحروف الأخيرة فتدور، فيكون:
- حرف
x
يصيرa
- حرف
y
يصيرb
- حرف
z
يصيرc
ومهمتنا تطبيقها بخوارزمية تأخذ نصًّا وتعميه.
أولاً نعرف الحروف الكبيرة والصغيرة:
تذكر أن لدينا في بايثون الفعل: str.index(sub: str) -> int
حيث يأخذ هذا الفعل جُزءًا نصيًّا ويأتي على النص من أوله إلى آخر باحثًا عن موضعه. ونحن سنستعمل ذلك في البحث عن موضع الحرف فسيكون على النحو التالي:
print(alphabet_lower.index('a'))
print(alphabet_lower.index('z'))
0
25
والأمر الآخر الذي سيفيدنا في تدوير الأرقام هو:
الحسابيات المقاسية (Modular Arithmetic) تتحرك فيه الأرقام بالجمع والضرب ونحوه بحيث تلتف الأرقام حول بعضها البعض عند الوصول إلى قيمة معينة، تسمى القياس (Modulus).
وهذه العملية في بايثون هي %
وتعبر عن باقي القسمة:
9 + 4) % 12 (
1
والآن نعرف الدالة التي ستأخذ حرفًا (char
) ومقدار الإزاحة (shift
) لتعيد الحرف البديل عنه بعد الإزاحة.
def encode(char: str, shift: int) -> str:
if char in alphabet_lower:
= alphabet_lower
alphabet elif char in alphabet_upper:
= alphabet_upper
alphabet else:
return char
= alphabet.index(char)
code = (code + shift) % len(alphabet)
new_code = alphabet[new_code]
new_char return new_char
- أولاً نبحث عن الحرف في سلسلة الأحرف الصغيرة، ثم الكبيرة، ثم إن لم يوجد في أي منها فإننا نعيد الحرف كما هو، ولا نزيحه
- فإن وجدنا الحرف في إحدى السلسلتين (الكبيرة أو الصغيرة) فإننا نعين المتغير
alphabet
لهذه السلسلة التي سيتم البحث فيها بعد ذلك - نبحث في هذه السلسلة بالفعل
.index()
لنعرف موضِع الحرف، حيث سيكون هذا هو رمزه الرقمي (code
). - نزيح الرقم بالمقدار المعطى (
shift
) ونُدير الناتج بعملية باقي القسمة وهي التعبير: (% len(alphabet)
) - نعين المتغير
new_char
للحرف الذي في موضع الرقم بعد الإزاحة - نعيد الحرف البديل
نختبر الدالة ببعض التوكيادت:
assert encode('a', 3) == 'd'
assert encode('b', 3) == 'e'
assert encode('x', 3) == 'a'
assert encode('y', 3) == 'b'
أما دالة عكس التعمية، فهي ببساطة تعكس الإزاحة بطرح الرقم بعد جمعه.
def decode(char: str, shift: int) -> str:
if char in alphabet_lower:
= alphabet_lower
alphabet elif char in alphabet_upper:
= alphabet_upper
alphabet else:
return char
= alphabet.index(char)
code = (code - shift) % len(alphabet)
new_code = alphabet[new_code]
new_char return new_char
فأما دالة استخراج المعمى، فنريدها أن تكون فعلاً تعكِس، ولذلك سنستعمل التوكيد بتمرير المعمة لعكسه مباشرة بهذه الطريقة:
assert decode(encode('a', 3), 3) == 'a'
assert decode(encode('z', 3), 3) == 'z'
والآن بعد أن تأكدنا من عمل ذلك على مستوى الحرف الواحد، نريد أن نأخذ النص كاملاً فنعميه:
def encode_text(text: str, shift: int) -> str:
= ""
result for char in text:
+= encode(char, shift)
result return result
- نبدأ بنص فارغ:
result = ""
- نأتي على الحروف حرفًا حرفًا كما في النص
text
ونستعمل فعل التعميةencode
لذلك النص، ونضيف الحرف العائد إلى النص الناتجresult
أما عكس التعمية فباستعمال decode
بدلاً من encode
بمثل ما تقدَّم:
def decode_text(text: str, shift: int) -> str:
= ""
result for char in text:
+= decode(char, shift)
result return result
والآن نريد أن نختبر، ولذلك سنستعين بموقع تفاعلي قد تم فيه عمل ذلك قبلنا، وهو موقع: cryptii.com فنأخذ النص ونستعمله ونتأكد أن ما نخرج به نفس ما خرجوا به:
assert (
"If he had anything confidential to say, he wrote it in cipher, that is, by so changing the order of the letters of the alphabet, that not a word could be made out", 3) ==
encode_text("Li kh kdg dqbwklqj frqilghqwldo wr vdb, kh zurwh lw lq flskhu, wkdw lv, eb vr fkdqjlqj wkh rughu ri wkh ohwwhuv ri wkh doskdehw, wkdw qrw d zrug frxog eh pdgh rxw"
)
أخيرًا نستعمل ذلك بعد أن تأكدنا من صحته كاملاً:
= "If he had anything confidential to say, he wrote it in cipher, that is, by so changing the order of the letters of the alphabet, that not a word could be made out."
text
print("## Text")
print(text)
= encode_text(text, 3)
text_encoded = decode_text(text_encoded, 3)
text_decoded
print("## Encoded")
print(text_encoded)
print("## Decoded")
print(text_decoded)
## Text
If he had anything confidential to say, he wrote it in cipher, that is, by so changing the order of the letters of the alphabet, that not a word could be made out.
## Encoded
Li kh kdg dqbwklqj frqilghqwldo wr vdb, kh zurwh lw lq flskhu, wkdw lv, eb vr fkdqjlqj wkh rughu ri wkh ohwwhuv ri wkh doskdehw, wkdw qrw d zrug frxog eh pdgh rxw.
## Decoded
If he had anything confidential to say, he wrote it in cipher, that is, by so changing the order of the letters of the alphabet, that not a word could be made out.
رسالة في استخراج المعمى
عهد الخليفة المأمون إلى الكندي، وهو أبو يُوسُفَ يَعْقُوبُ بنُ إِسْحاقَ الكِنْدِيُّ (185- 256 هـ / 801- 873 م) بإدارة بيت الحكمة، وكان ذلك بعد إتمام دراسته، حيث بدأ العمل في ترجمة المخطوطات اليونانية لأرسطو وغيره من الفلاسفة إلى اللغة العربية. واجه الكندي أول مرة خلال عمله الحاجة إلى استخراج النصوص المعماة، حيث كانت بعض المخطوطات التي كان عليه ترجمتها معماة.
قدم الكندي جدولاً بالتكرارات المطلقة لحروف الأبجدية العربية، والتي حسبها في عينة من سبع أوراق من النص.
الحرف | تكراره | الحرف | تكراره | الحرف | تكراره | الحرف | تكراره |
---|---|---|---|---|---|---|---|
ا | 600 | ر | 155 | س | 91 | ش | — |
ل | 437 | ع | 131 | ق | 63 | ض | — |
م | 320 | ف | 122 | ح | 57 | خ | — |
ه | 273 | ت | 120 | ج | 46 | ث | 17 |
و | 262 | ب | 112 | ذ | 35 | ط | 15 |
ي | 252 | ك | 112 | ص | 32 | غ | 15 |
ن | 221 | د | 92 | خ | 20 | ظ | 8 |
ملاحظة: لم يشر المؤلف إلى تكرارات حروف الشين والضاد والخاء، مع الإشارة إلى مكانها في الجدول، مرتبة حسب الترتيب التنازلي للتكرارات.
يصف المؤلف طريقة استخراج المعمى التكراري على النحو التالي:
فمما نحتال به لاستنباط الكتاب المعمى إذا عرف بأي لسان هو، أن يوجد من ذلك اللسان كتاب قدر ما يقع في جلد أو ما أشبهه، فنعد ما فيه من كل نوع من أنواع حروفه، فنكتب على أكثرها عددًا الأول، والذي يليه في الكثرة الثاني، والذي يلى ذلك في الكثرة الثالث، وكذلك حتى نأتي على جميع أنواع الحروف، ثم ننظر في الكتاب الذي نريد استخراجه فنصنف أيضًا أنواع صوره، فتنظر إلى أكثرها عددًا، فنسمه بسمة الحرف الأول، والذي يليه في الكثرة فنسمه بسمة الحرف الثاني، والذي يليه في الكثرة فنسمه بسمة الحرف الثالث، ثم كذلك حتى تنفد أنواع صور حروف الكتاب المعماة التي قصد لاستنباطه
المسألة
اكتب خوارزمية استخراج المعمى، كما بينها الكندي. وذلك يعني أننا لو جئنا بنصٍّ معمَّى بشفرة قيصر ولا تعلم مقدار الإزاحة، فإنك تستطيع أعادة النص الأصلي.