مرحبا يا هبر! أقدم لكم ترجمة المقال "تشفير جافا" لجاكوب جنكوف.
هذا المنشور هو ترجمة لأول مقالة تشفير Java من سلسلة من المقالات للمبتدئين الذين يرغبون في تعلم أساسيات التشفير في Java.
جدول المحتويات:
- تشفير جافا
- صفر
- MessageDigest
- ماك
- توقيع
- KeyPair
- KeyGenerator
- KeyPairGenerator
- تخزين المفاتيح
- Keytool
- شهادة
- CertificateFactory
- CertPath
تشفير جافا
يوفر Java Cryptography API إمكانية تشفير وفك تشفير البيانات في جافا ، وكذلك إدارة المفاتيح والتوقيعات ومصادقة (مصادقة) الرسائل ، وحساب تجزئة التشفير وأكثر من ذلك بكثير.
تشرح هذه المقالة أساسيات كيفية استخدام Java Cryptography API لأداء مهام متنوعة تتطلب تشفيرًا آمنًا.
لا تشرح هذه المقالة أساسيات نظرية التشفير. سوف تضطر إلى رؤية هذه المعلومات في مكان آخر.
تمديد تشفير جافا
يتم توفير API تشفير Java بواسطة ما يسمى Java Cryptography Extension (JCE). منذ فترة طويلة JCE جزء من منصة جافا. في البداية ، تم فصل JCE عن Java بسبب قيود التصدير على تقنية التشفير في الولايات المتحدة. لذلك ، لم يتم تضمين أقوى خوارزميات التشفير في نظام Java الأساسي. يمكن تطبيق خوارزميات التشفير الأكثر قوة إذا كانت شركتك موجودة في الولايات المتحدة الأمريكية ، ولكن في حالات أخرى ، يتعين عليك استخدام خوارزميات أضعف أو تنفيذ خوارزميات التشفير الخاصة بك وتوصيلها بـ JCE.
منذ عام 2017 ، تم تخفيف قواعد تصدير خوارزميات التشفير في الولايات المتحدة بشكل كبير ، وفي معظم أنحاء العالم يمكنك استخدام معايير التشفير الدولية من خلال Java JCE.
جافا تشفير العمارة
Java Cryptography Architecture (JCA) هو اسم تصميم API الداخلي للتشفير في Java. ويتمحور JCA حول عدة فئات أساسية وواجهات للأغراض العامة. يتم توفير الوظيفة الفعلية لهذه الواجهات من قبل الموردين. وبالتالي ، يمكنك استخدام فئة Cipher لتشفير وفك تشفير بعض البيانات ، ولكن التنفيذ المحدد للشفرات (خوارزمية التشفير) يعتمد على الموفر المعين المستخدم.
يمكنك أيضًا تنفيذ مقدمي خدماتك وتوصيلهم ، لكن يجب أن تكون حذراً في هذا الأمر. من الصعب تنفيذ التشفير دون ثغرات أمنية! إذا كنت لا تعرف ما تفعله ، فربما يكون من الأفضل لك استخدام موفر Java المدمج أو استخدام موفر موثوق مثل Bouncy Castle.
الفئات الرئيسية والواجهات
تتكون واجهة برمجة تطبيقات تشفير Java من حزم Java التالية:
- java.security
- java.security.cert
- java.security.spec
- java.security.interfaces
- javax.crypto
- javax.crypto.spec
- javax.crypto.interfaces
الفئات الرئيسية وواجهات هذه الحزم:
- مزود
- SecureRandom
- صفر
- MessageDigest
- توقيع
- ماك
- AlgorithmParameters
- AlgorithmParameterGenerator
- KeyFactory
- SecretKeyFactory
- KeyPairGenerator
- KeyGenerator
- KeyAgreement
- تخزين المفاتيح
- CertificateFactory
- CertPathBuilder
- CertPathValidator
- CertStore
مزود (مزود التشفير)
فئة الموفر (java.security.Provider) هي الفئة المركزية في Java crypto API. من أجل استخدام Java crypto API ، تحتاج إلى تثبيت موفر تشفير. يأتي Java SDK مع موفر التشفير الخاص به. ما لم تقم بتعيين موفر التشفير بشكل صريح ، فسيتم استخدام الموفر الافتراضي. ومع ذلك ، قد لا يدعم موفر التشفير هذا خوارزميات التشفير التي تريد استخدامها. لذلك ، قد تضطر إلى تثبيت موفر التشفير الخاص بك.
يُطلق على Bouncy Castle أحد أشهر مزودي التشفير في واجهة برمجة تطبيقات تشفير Java. فيما يلي مثال حيث تم تعيين BouncyCastleProvider كموفر التشفير:
import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; public class ProviderExample { public static void main(String[] args) { Security.addProvider(new BouncyCastleProvider()); } }
الشفرات (معرف)
تمثل فئة التشفير (javax.crypto.Cipher) خوارزمية تشفير. يمكن استخدام الشفرة للتشفير وفك تشفير البيانات. يتم شرح فئة Cipher بمزيد من التفاصيل في الأقسام التالية ، مع وصف موجز أدناه.
إنشاء مثيل لفئة التشفير التي تستخدم خوارزمية تشفير AES للاستخدام الداخلي:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
تقبل طريقة Cipher.getInstance (...) سلسلة تحدد خوارزمية التشفير التي يجب استخدامها ، وكذلك بعض معلمات الخوارزمية الأخرى.
في المثال أعلاه:
- AES - خوارزمية التشفير
- CBC هو وضع يمكن أن تعمل فيه خوارزمية AES.
- PKCS5Padding هي كيفية معالجة خوارزمية AES لآخر بايت من البيانات للتشفير. ماذا يعني هذا بالضبط ، انظر في دليل التشفير ككل ، وليس في هذه المقالة.
تهيئة التشفير
قبل استخدام مثيل التشفير ، يجب عليك تهيئة. تتم تهيئة مثيل التشفير عن طريق استدعاء الأسلوب init () . تأخذ طريقة init () معلمتين:
- الوضع - التشفير / فك التشفير
- مفتاح
تشير المعلمة الأولى إلى طريقة تشغيل مثيل التشفير: لتشفير البيانات أو فك تشفيرها. تشير المعلمة الثانية إلى المفتاح الذي يستخدمونه لتشفير البيانات أو فك تشفيرها.
مثال:
byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; String algorithm = "RawBytes"; SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm); cipher.init(Cipher.ENCRYPT_MODE, key);
يرجى ملاحظة أن طريقة إنشاء المفاتيح في هذا المثال غير آمنة ويجب عدم استخدامها في الممارسة العملية. تشرح هذه المقالة في الأقسام التالية كيفية إنشاء المفاتيح بشكل أكثر أمانًا.
لتهيئة مثيل تشفير لفك تشفير البيانات ، يجب عليك استخدام Cipher.DECRYPT_MODE ، على سبيل المثال:
cipher.init(Cipher.DECRYPT_MODE, key);
تشفير البيانات أو فك التشفير
بعد تهيئة التشفير ، يمكنك البدء في تشفير البيانات أو فك تشفيرها عن طريق استدعاء أساليب التحديث () أو doFinal () . يتم استخدام طريقة التحديث () إذا كنت تقوم بتشفير أو فك تشفير جزء من البيانات. يتم استدعاء الأسلوب doFinal () عند تشفير آخر جزء من البيانات أو إذا كانت كتلة البيانات التي تقوم بتمريرها إلى doFinal () عبارة عن مجموعة واحدة من البيانات للتشفير.
مثال على تشفير البيانات باستخدام طريقة doFinal () :
byte[] plainText = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8"); byte[] cipherText = cipher.doFinal(plainText);
لفك تشفير البيانات ، تحتاج إلى تمرير النص المشفر (البيانات) إلى طريقة doFinal () أو doUpdate () .
مفاتيح (المفاتيح)
لتشفير أو فك تشفير البيانات ، تحتاج إلى مفتاح. هناك نوعان من المفاتيح ، اعتمادًا على نوع خوارزمية التشفير المستخدمة:
- مفاتيح متناظرة
- مفاتيح غير متماثلة
تُستخدم المفاتيح المتماثلة لخوارزميات التشفير المتناظر. تستخدم خوارزميات التشفير المتماثل نفس المفتاح للتشفير وفك التشفير.
تُستخدم المفاتيح غير المتماثلة لخوارزميات التشفير غير المتماثلة. تستخدم خوارزميات التشفير غير المتماثلة مفتاحًا للتشفير ومفتاحًا آخر لفك التشفير. خوارزميات تشفير المفتاح العام والخاص هي أمثلة على خوارزميات تشفير غير متماثلة.
بطريقة ما ، يجب على الطرف الذي يحتاج إلى فك تشفير البيانات معرفة المفتاح اللازم لفك تشفير البيانات. إذا لم يكن برنامج فك التشفير طرفًا في تشفير البيانات ، فيجب على الطرفين الاتفاق على مفتاح أو تبادل مفتاح. وهذا ما يسمى تبادل المفتاح.
مفتاح الأمن
يجب أن يكون من الصعب تخمين المفاتيح بحيث يتعذر على المهاجم اختيار مفتاح تشفير بسهولة. في المثال الوارد في القسم السابق من فئة Cipher ، تم استخدام مفتاح بسيط للغاية مشفر. في الممارسة العملية ، هذا لا يستحق القيام به. إذا كان من السهل تخمين مفتاح الأطراف ، فسيكون من السهل على المهاجم فك تشفير البيانات المشفرة ، وربما إنشاء رسائل مزيفة من تلقاء نفسه. من المهم عمل مفتاح يصعب تخمينه. وبالتالي ، يجب أن يتكون المفتاح من وحدات البايت العشوائية. كلما زاد عدد البايتات العشوائية ، زاد صعوبة تخمينها ، لأن هناك المزيد من المجموعات الممكنة.
جيل المفتاح
لإنشاء مفاتيح تشفير عشوائية ، يمكنك استخدام فئة Java KeyGenerator. سيتم شرح KeyGenerator بمزيد من التفصيل في الفصول التالية ، فيما يلي مثال صغير لاستخدامه هنا:
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); SecureRandom secureRandom = new SecureRandom(); int keyBitSize = 256; keyGenerator.init(keyBitSize, secureRandom); SecretKey secretKey = keyGenerator.generateKey();
يمكن تمرير مثيل SecretKey الناتج إلى أسلوب Cipher.init () ، على سبيل المثال مثل هذا:
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
جيل زوج الرئيسية
تستخدم خوارزميات التشفير غير المتماثلة زوج مفاتيح يتكون من مفتاح عام ومفتاح خاص لتشفير البيانات وفك تشفيرها. لإنشاء زوج مفاتيح غير متماثل ، يمكنك استخدام KeyPairGenerator (java.security.KeyPairGenerator). سيتم وصف KeyPairGenerator بمزيد من التفصيل في الفصول التالية ، فيما يلي مثال بسيط لاستخدام Java KeyPairGenerator:
SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); KeyPair keyPair = keyPairGenerator.generateKeyPair();
مفتاح المتجر
Java KeyStore هي قاعدة بيانات يمكن أن تحتوي على مفاتيح. يمثل Java KeyStore فئة KeyStore (java.security.KeyStore). يمكن أن يحتوي مفتاح المفاتيح على مفاتيح من الأنواع التالية:
- مفاتيح خاصة
- المفاتيح والشهادات العامة (المفاتيح العامة + الشهادات)
- مفاتيح سرية
يتم استخدام المفاتيح الخاصة والعامة في تشفير غير متماثل. قد يكون لدى المفتاح العمومي شهادة مرتبطة. الشهادة هي وثيقة تثبت هوية شخص أو مؤسسة أو جهاز يدعي امتلاكه لمفتاح عام.
وعادة ما يتم توقيع الشهادة رقميا من قبل الطرف المعتمد كدليل.
تُستخدم المفاتيح الخاصة في تشفير متماثل ، فئة KeyStore معقدة جدًا ، ولهذا السبب يتم وصفها بمزيد من التفاصيل لاحقًا في فصل منفصل على Java KeyStore.
Java Keytool هي أداة سطر أوامر يمكنها العمل مع ملفات Java KeyStore. Keytool يمكن أن تولد أزواج المفاتيح في ملف KeyStore ، شهادات التصدير واستيراد الشهادات إلى KeyStore وبعض الوظائف الأخرى. Keytool يأتي مع تثبيت جافا. تم وصف Keytool بمزيد من التفاصيل لاحقًا في فصل منفصل على Java Keytool.
رسالة دايجست
عندما تتلقى بيانات مشفرة من الجانب الآخر ، هل يمكنك التأكد من عدم تغيير أي شخص للبيانات المشفرة في الطريق إليك؟
عادةً ما يكون الحل هو حساب خلاصة الرسالة من البيانات قبل تشفيرها ، ثم تشفير البيانات وخلاصة الرسالة وإرسالها عبر الشبكة. ملخص الرسالة هو قيمة تجزئة محسوبة بناءً على بيانات الرسالة. إذا تم تغيير بايت واحد على الأقل في البيانات المشفرة ، فسيتغير أيضًا ملخص الرسالة المحسوب من البيانات.
عندما تتلقى بيانات مشفرة ، تقوم بفك تشفيرها ، وحساب ملخص الرسالة منها ، ومقارنة ملخص الرسالة المحسوبة مع ملخص الرسالة المرسلة مع البيانات المشفرة. إذا كانت عمليات هضم الرسائل متماثلة ، فهناك احتمال كبير (ولكن ليس بنسبة 100٪) بعدم تغيير البيانات.
يمكن استخدام Java MessageDigest (java.security.MessageDigest) لحساب ملخصات الرسائل. لإنشاء مثيل لـ MessageDigest ، يتم استدعاء الأسلوب MessageDigest.getInstance () . هناك العديد من خوارزميات هضم الرسائل المختلفة. تحتاج إلى تحديد الخوارزمية التي تريد استخدامها عند إنشاء مثيل MessageDigest. سيتم وصف العمل مع MessageDigest بمزيد من التفصيل في الفصل Java MessageDigest.
مقدمة موجزة عن فئة MessageDigest:
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
ينشئ هذا المثال مثيل MessageDigest يستخدم خوارزمية تجزئة التشفير الداخلي SHA-256 لحساب ملخصات الرسائل.
لحساب ملخص الرسالة لبعض البيانات ، يمكنك استدعاء الأسلوب update () أو digest () . يمكن استدعاء أسلوب التحديث () عدة مرات ، ويتم تحديث ملخص الرسالة داخل الكائن. عندما تقوم بتمرير كافة البيانات التي تريد تضمينها في ملخص الرسالة ، يمكنك استدعاء digest () واسترداد ملخص ملخص الرسالة.
مثال على استدعاء التحديث () عدة مرات ، يليه استدعاء للهضم () :
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] data1 = "0123456789".getBytes("UTF-8"); byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); messageDigest.update(data1); messageDigest.update(data2); byte[] digest = messageDigest.digest();
يمكنك أيضًا استدعاء digest () مرة واحدة ، بتمرير جميع البيانات لحساب ملخص الرسالة. مثال:
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] data1 = "0123456789".getBytes("UTF-8"); byte[] digest = messageDigest.digest(data1);
رمز مصادقة الرسائل (MAC)
يتم استخدام فئة Java Mac لإنشاء MAC (رمز مصادقة الرسائل) من الرسالة. يشبه MAC ملخص الرسالة ، لكنه يستخدم مفتاحًا إضافيًا لتشفير ملخص الرسائل. فقط مع وجود كل من البيانات المصدر والمفتاح ، يمكنك التحقق من MAC. وبالتالي ، يعد MAC وسيلة أكثر أمانًا لحماية كتلة بيانات من التعديل من خلاصة الرسالة. تم توضيح فئة Mac في مزيد من التفاصيل في فصل Java Mac ، يليها مقدمة مختصرة.
يتم إنشاء مثيل Java Mac عن طريق استدعاء الأسلوب Mac.getInstance () ، مع تمرير اسم الخوارزمية لاستخدامها كمعلمة. إليك ما يبدو عليه:
Mac mac = Mac.getInstance("HmacSHA256");
قبل إنشاء MAC من البيانات ، يجب تهيئة مثيل Mac باستخدام المفتاح. فيما يلي مثال على تهيئة مثيل Mac باستخدام مفتاح:
byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8 ,9,10,11,12,13,14,15}; String algorithm = "RawBytes"; SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm); mac.init(key);
بعد تهيئة مثيل Mac ، يمكنك حساب MAC من البيانات عن طريق استدعاء أساليب التحديث () و doFinal () . إذا كان لديك جميع البيانات لحساب MAC ، فيمكنك الاتصال على الفور بأسلوب doFinal () . إليك ما يبدو عليه:
byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); byte[] data2 = "0123456789".getBytes("UTF-8"); mac.update(data); mac.update(data2); byte[] macBytes = mac.doFinal();
التوقيع (توقيع)
يتم استخدام فئة التوقيع (java.security.Signature) لتوقيع البيانات رقميًا. عند توقيع البيانات ، يتم إنشاء توقيع رقمي من هذه البيانات. وبالتالي ، يتم فصل التوقيع عن البيانات.
يتم إنشاء توقيع رقمي عن طريق إنشاء ملخص الرسالة (تجزئة) من البيانات وتشفير ملخص الرسالة هذا باستخدام المفتاح الخاص للجهاز أو الشخص أو المؤسسة التي يجب أن توقع البيانات. يسمى ملخص الرسالة المشفرة بالتوقيع الرقمي.
لإنشاء مثيل للتوقيع ، يتم استدعاء الأسلوب Signature.getInstance (...) :
Signature signature = Signature.getInstance("SHA256WithDSA");
توقيع البيانات
لتوقيع البيانات ، يجب تهيئة مثيل التوقيع في وضع التوقيع عن طريق استدعاء طريقة initSign (...) ، بتمرير المفتاح الخاص لتوقيع البيانات. مثال على تهيئة مثيل التوقيع في وضع التوقيع:
signature.initSign(keyPair.getPrivate(), secureRandom);
بعد تهيئة مثيل التوقيع ، يمكن استخدامه لتوقيع البيانات. يتم ذلك عن طريق استدعاء أسلوب التحديث () ، تمرير بيانات التوقيع كمعلمة. يمكنك استدعاء طريقة التحديث () عدة مرات لاستكمال البيانات لإنشاء التوقيع. بعد تمرير جميع البيانات إلى طريقة التحديث () ، يتم استدعاء طريقة التوقيع () للحصول على توقيع رقمي. إليك ما يبدو عليه:
byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature.update(data); byte[] digitalSignature = signature.sign();
التحقق من التوقيع
للتحقق من التوقيع ، تحتاج إلى تهيئة مثيل التوقيع في وضع التحقق عن طريق استدعاء طريقة initVerify (...) ، وتمرير المفتاح العمومي كمعلمة ، والتي يتم استخدامها للتحقق من التوقيع. مثال على تهيئة مثيل التوقيع في وضع التحقق يبدو كما يلي:
Signature signature = Signature.getInstance("SHA256WithDSA"); signature.initVerify(keyPair.getPublic());
بعد التهيئة في وضع التحقق ، يتم نقل البيانات الموقعة إلى طريقة التحديث () . إرجاع استدعاء () للتحقق من صحة الأسلوب أو خطأ اعتمادا على ما إذا كان يمكن التحقق من التوقيع أم لا. هنا هو التحقق من التوقيع:
byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature2.update(data2); boolean verified = signature2.verify(digitalSignature);
مثال كامل على التوقيع والتحقق
SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); KeyPair keyPair = keyPairGenerator.generateKeyPair(); Signature signature = Signature.getInstance("SHA256WithDSA"); signature.initSign(keyPair.getPrivate(), secureRandom); byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature.update(data); byte[] digitalSignature = signature.sign(); Signature signature2 = Signature.getInstance("SHA256WithDSA"); signature2.initVerify(keyPair.getPublic()); signature2.update(data); boolean verified = signature2.verify(digitalSignature); System.out.println("verified = " + verified);