מבוא ל- BouncyCastle עם Java

1. סקירה כללית

BouncyCastle היא ספריית Java המשלימה את סיומת הקריפטוגרפיה המוגדרת כברירת מחדל (JCE).

במאמר פתיחה זה, אנו נראה כיצד להשתמש ב- BouncyCastle לביצוע פעולות הצפנה, כגון הצפנה וחתימה.

2. תצורת Maven

לפני שנתחיל לעבוד עם הספרייה, עלינו להוסיף את התלות הנדרשת pom.xml קוֹבֶץ:

 org.bouncycastle bcpkix-jdk15on 1.58 

שים לב שאנחנו תמיד יכולים לחפש את גרסאות התלות האחרונות במאגר המרכזי של Maven.

3. הגדרת קבצי מדיניות שיפוט חוזק ללא הגבלה

התקנת Java הסטנדרטית מוגבלת מבחינת חוזק עבור פונקציות הצפנה, זאת בשל מדיניות האוסרת על שימוש במפתח בגודל העולה על ערכים מסוימים למשל. 128 ל- AES.

כדי להתגבר על המגבלה הזו, עלינו הגדר את קבצי מדיניות השיפוט ללא הגבלה.

על מנת לעשות זאת, ראשית עלינו להוריד את החבילה באמצעות קישור זה. לאחר מכן עלינו לחלץ את קובץ הרוכסן לספריה לבחירתנו - המכילה שני קבצי צנצנת:

  • local_policy.jar
  • US_export_policy.jar

לבסוף, עלינו לחפש את ה- {JAVA_HOME} / lib / אבטחה התיקיה והחלף את קבצי המדיניות הקיימים בקבצים שחילצנו כאן.

ציין זאת ב- Java 9, אנחנו כבר לא צריכים להוריד את חבילת קבצי המדיניות, הגדרת ה- crypto.policy נכס ל ללא הגבלה זה מספיק:

Security.setProperty ("crypto.policy", "ללא הגבלה");

לאחר שתסיים, עלינו לבדוק שהתצורה עובדת כהלכה:

int maxKeySize = javax.crypto.Cipher.getMaxAllowedKeyLength ("AES"); System.out.println ("גודל מפתח מקסימלי עבור AES:" + maxKeySize);

כתוצאה:

גודל מפתח מקסימלי עבור AES: 2147483647

מבוסס על גודל המפתח המרבי שהוחזר על ידי ה- getMaxAllowedKeyLength () בשיטה, אנו יכולים לומר בבטחה שקבצי מדיניות הכוח הבלתי מוגבלים הותקנו כהלכה.

אם הערך שהוחזר שווה ל- 128, עלינו לוודא שהתקנו את הקבצים ב- JVM שבו אנו מריצים את הקוד.

4. פעולות קריפטוגרפיות

4.1. הכנת תעודה ומפתח פרטי

לפני שנקפוץ ליישום פונקציות הצפנה, ראשית עלינו ליצור אישור ומפתח פרטי.

למטרות בדיקה, אנו יכולים להשתמש במשאבים הבאים:

  • Baeldung.cer
  • Baeldung.p12 (סיסמה = "סיסמה")

Baeldung.cer היא תעודה דיגיטלית המשתמשת בתקן תשתיות מפתח ציבורי בינלאומי X.509, בעוד ש- Baeldung.p12 הוא PKCS12 Keystore המוגן באמצעות סיסמה המכיל מפתח פרטי.

בואו נראה כיצד ניתן לטעון את אלה ב- Java:

Security.addProvider (חדש BouncyCastleProvider ()); CertificateFactory certFactory = CertificateFactory .getInstance ("X.509", "BC"); תעודת X509Certificate = (X509Certificate) certFactory .generateCertificate (FileInputStream חדש ("Baeldung.cer")); char [] keystorePassword = "סיסמה" .toCharArray (); char [] keyPassword = "סיסמה" .toCharArray (); KeyStore keystore = KeyStore.getInstance ("PKCS12"); keystore.load (FileInputStream חדש ("Baeldung.p12"), keystorePassword); מקש PrivateKey = (PrivateKey) keystore.getKey ("baeldung", keyPassword);

ראשית, הוספנו את ה- BouncyCastleProvider כספק אבטחה המשתמש באופן דינמי ב- addProvider () שיטה.

ניתן לעשות זאת גם באופן סטטי על ידי עריכת ה- {JAVA_HOME} /jre/lib/security/java.security קובץ, והוספת שורה זו:

security.provider.N = org.bouncycastle.jce.provider.BouncyCastleProvider

לאחר שהספק מותקן כראוי, יצרנו תעודת מפעל אובייקט באמצעות getInstance () שיטה.

ה getInstance () השיטה לוקחת שני טיעונים; סוג האישור "X.509" וספק האבטחה "BC".

ה certFactory מופע משמש בהמשך ליצירת X509 תעודה אובייקט, דרך ה- createCertificate () שיטה.

באותו האופן, יצרנו אובייקט PKCS12 Keystore, עליו ה לִטעוֹן() שיטה נקראת.

ה להשיג מפתח() השיטה מחזירה את המפתח הפרטי המשויך לכינוי נתון.

שים לב ש- PKCS12 Keystore מכיל קבוצה של מפתחות פרטיים, לכל מפתח פרטי יכולה להיות סיסמה ספציפית, לכן אנו זקוקים לסיסמה גלובלית כדי לפתוח את Keystore, ולמפתח מסוים כדי לאחזר את המפתח הפרטי.

האישור וזוג המפתחות הפרטיים משמשים בעיקר בפעולות הצפנה א-סימטריות:

  • הצפנה
  • פענוח
  • חֲתִימָה
  • אימות

4.2 הצפנה ופענוח CMS / PKCS7

בקריפטוגרפיה של הצפנה אסימטרית, כל תקשורת דורשת אישור ציבורי ומפתח פרטי.

הנמען מחויב לאישור המשותף באופן ציבורי בין כל השולחים.

במילים פשוטות, השולח זקוק לאישור הנמען כדי להצפין הודעה, ואילו הנמען צריך את המפתח הפרטי המשויך בכדי שיוכל לפענח אותה.

בואו נסתכל כיצד ליישם encryptData () פונקציה, באמצעות תעודת הצפנה:

בתים סטטיים ציבוריים [] encryptData (בתים [] נתונים, X509Certificate encryptionCertificate) זורק CertificateEncodingException, CMSException, IOException {byte [] encryptedData = null; if (null! = data && null! = encryptionCertificate) {CMSEnvelopedDataGenerator cmsEnvelopedDataGenerator = חדש CMSEnvelopedDataGenerator (); JceKeyTransRecipientInfoGenerator jceKey = חדש JceKeyTransRecipientInfoGenerator (encryptionCertificate); cmsEnvelopedDataGenerator.addRecipientInfoGenerator (transKeyGen); CMSTypedData msg = חדש CMSProcessableByteArray (נתונים); מקודד OutputEncryptor = JceCMSContentEncryptorBuilder חדש (CMSAlgorithm.AES128_CBC) .setProvider ("BC"). Build (); CMSEnvelopedData cmsEnvelopedData = cmsEnvelopedDataGenerator .generate (msg, encryptor); encryptedData = cmsEnvelopedData.getEncoded (); } להחזיר את encryptedData; }

יצרנו JceKeyTransRecipientInfoGenerator התנגד באמצעות תעודת הנמען.

לאחר מכן, יצרנו חדש CMSEnvelopedDataGenerator התנגד והוסיף לתוכו את מחולל המידע הנמען.

לאחר מכן השתמשנו ב- JceCMSContentEncryptorBuilder בכיתה ליצור OutputEncrytor אובייקט, תוך שימוש באלגוריתם AES CBC.

המוצפן משמש מאוחר יותר ליצירת a CMSEnvelopedData אובייקט שעוטף את ההודעה המוצפנת.

לבסוף, הייצוג המקודד של המעטפה מוחזר כמערך בתים.

עכשיו, בואו נראה מה היישום של ה- decryptData () השיטה נראית כמו:

בתים סטטיים ציבוריים [] decryptData (בתים [] encryptedData, PrivateKey decryptionKey) זורק CMSException {בייט [] decryptedData = null; אם (null! = encryptedData && null! = decryptionKey) {CMSEnvelopedData envelopedData = CMSEnvelopedData חדשים (encryptedData); מקבלי אוספים = envelopedData.getRecipientInfos (). GetRecipients (); KeyTransRecipientInformation recipientInfo = (KeyTransRecipientInformation) recipients.iterator (). הבא (); נמען JceKeyTransRecipient = חדש JceKeyTransEnvelopedRecipient (decryptionKey); החזר recipientInfo.getContent (מקבל); } להחזיר decryptedData; }

ראשית, אתחלנו א CMSEnvelopedData התנגד באמצעות מערך בתים המוצפנים, ואז אחזרנו את כל מקבלי ההודעה המיועדים באמצעות ה- getRecipients () שיטה.

לאחר שסיימנו, יצרנו חדש נמען JceKeyTrans אובייקט המשויך למפתח הפרטי של הנמען.

ה recipientInfo מופע מכיל את ההודעה המפוענחת / מקודדת, אך איננו יכולים לאחזר אותה אלא אם יש לנו את מפתח הנמען המתאים.

לבסוף, בהתחשב במפתח המקבל כטיעון, ה- getContent () השיטה מחזירה את מערך בתים גולמי שחולץ מה- מעטפות נתונים הנמען הזה משויך אליו.

בואו לכתוב מבחן פשוט כדי לוודא שהכל עובד בדיוק כמו שצריך:

String secretMessage = "הסיסמה שלי היא 123456 Seven"; System.out.println ("הודעה מקורית:" + secretMessage); בתים [] stringToEncrypt = secretMessage.getBytes (); בתים [] encryptedData = encryptData (stringToEncrypt, אישור); System.out.println ("הודעה מוצפנת:" + מחרוזת חדשה (encryptedData)); בתים [] rawData = decryptData (encryptedData, privateKey); מחרוזת decryptedMessage = מחרוזת חדשה (rawData); System.out.println ("הודעה מפוענחת:" + decryptedMessage);

כתוצאה:

הודעה מקורית: הסיסמה שלי היא 123456 Seven הודעה מוצפנת: 0  *  H   ... הודעה מפוענחת: הסיסמה שלי היא 123456Seven

4.3 חתימה ואימות CMS / PKCS7

חתימה ואימות הן פעולות הצפנה המאמתות את האותנטיות של הנתונים.

בואו נראה כיצד לחתום על הודעה סודית באמצעות אישור דיגיטלי:

בתים סטטיים ציבוריים [] signData (נתוני בתים [], X509Certificate SigningCertificate, PrivateKey SigningKey) זורקים חריגה {בייט [] חתום הודעה = null; רשימת certList = ArrayList חדש (); CMSTypedData cmsData = CMSProcessableByteArray חדש (נתונים); certList.add (SigningCertificate); סרטי חנות = JcaCertStore חדש (certList); CMSSignedDataGenerator cmsGenerator = CMSSignedDataGenerator חדש (); ContentSigner contentSigner = חדש JcaContentSignerBuilder ("SHA256withRSA"). Build (SigningKey); cmsGenerator.addSignerInfoGenerator (JcaSignerInfoGeneratorBuilder חדש (JcaDigestCalculatorProviderBuilder חדש (). setProvider ("BC") .build ()). build (contentSigner, SigningCertificate)); cmsGenerator.addCertificates (certs); CMSSignedData cms = cmsGenerator.generate (cmsData, נכון); signMessage = cms.getEncoded (); החזר חתום הודעה; } 

ראשית, שילבנו את הקלט ל- a CMSTypedDataלאחר מכן, יצרנו חדש CMSSignedDataGenerator לְהִתְנַגֵד.

השתמשנו SHA256 עם RSA כאלגוריתם חתימות, ומפתח החתימה שלנו ליצירת חדש ContentSigner לְהִתְנַגֵד.

ה contentSigner מופע משמש לאחר מכן, יחד עם אישור החתימה כדי ליצור SigningInfoGenerator לְהִתְנַגֵד.

לאחר הוספת ה- SignerInfoGenerator ותעודת החתימה ל CMSSignedDataGenerator למשל, סוף סוף אנו משתמשים ב- לִיצוֹר() שיטה ליצירת אובייקט נתונים חתום CMS, הנושא גם חתימת CMS.

עכשיו כשראינו כיצד לחתום על נתונים, בואו נראה כיצד לאמת נתונים חתומים:

verifSignedData בוליטי סטטי ציבורי (בתים [] חתום נתונים) זורק חריג {X509Certificate signCert = null; ByteArrayInputStream inputStream = ByteArrayInputStream חדש (חתום נתונים); ASN1InputStream asnInputStream = ASN1InputStream חדש (inputStream); CMSSignedData cmsSignedData = CMSSignedData חדש (ContentInfo.getInstance (asnInputStream.readObject ())); SignerInformationStore signers = cmsSignedData.getCertificates (). GetSignerInfos (); SignerInformation signer = signers.getSigners (). Iterator (). הבא (); אוסף certCollection = certs.getMatches (signer.getSID ()); X509CertificateHolder certHolder = certCollection.iterator (). הבא (); החזרת חותם. verify (חדש JcaSimpleSignerInfoVerifierBuilder () .build (certHolder)); }

שוב, יצרנו CMSSignedData אובייקט המבוסס על מערך בתים הנתונים החתומים שלנו, לאחר מכן השגנו את כל החותמים המשויכים לחתימות באמצעות ה- getSignerInfos () שיטה.

בדוגמה זו, אימתנו חותם אחד בלבד, אך לשימוש כללי, חובה לחזור על אוסף החותמים שהוחזר על ידי getSigners () שיטה ולבדוק כל אחד בנפרד.

לבסוף, יצרנו SignerInformationVerifier אובייקט באמצעות לִבנוֹת() שיטה והעביר אותה ל תאשר() שיטה.

שיטת ה- verific () חוזרת נָכוֹן אם האובייקט הנתון יכול לאמת את החתימה בהצלחה על אובייקט החותם.

הנה דוגמה פשוטה:

בתא [] חתום נתונים = סימן נתונים (rawData, תעודה, privateKey); צ'ק בוליאני = verifSignData (חתום נתונים); System.out.println (סימון);

כתוצאה:

נָכוֹן

5. מסקנה

במאמר זה גילינו כיצד להשתמש בספריית BouncyCastle לביצוע פעולות הצפנה בסיסיות, כגון הצפנה וחתימה.

במצב של עולם אמיתי, לעתים קרובות אנו רוצים לחתום ואז להצפין את הנתונים שלנו, ככה, רק הנמען מסוגל לפענח אותם באמצעות המפתח הפרטי, ולבדוק את האותנטיות שלהם על סמך החתימה הדיגיטלית.

את קטעי הקוד ניתן למצוא כמו תמיד ב- GitHub.


$config[zx-auto] not found$config[zx-overlay] not found