رمزنگاری روشی برای محافظت از دادهها و ارتباطات با استفاده از کدها و کلیدهای دیجیتال است تا اطمینان حاصل شود اطلاعات بدون دستکاری به فرستنده مورد نظر برای پردازش بعدی تحویل داده میشوند.
درک مفاهیم اصلی رمزنگاری مانند رمزنگاری (Encryption) و رمزگشایی (Decryption) برای هر توسعهدهندهای حیاتی است، چون ممکن است روی ویژگیهای مرتبط با موارد زیر کار کنید:
چون رمزنگاری با دادهها کار میکند، دادهها یا به صورت متن ساده (Plaintext) (cleartext) هستند یا متن رمزنگاریشده (Ciphertext) (cryptogram). Plaintext به معنای این است که پیام در فرمت طبیعی و خوانا برای مهاجم است. Ciphertext به معنای این است که پیام برای مهاجم ناخوانا اما برای دریافتکننده مورد نظر خوانا است.
میتوانید پیام را از Plaintext به Ciphertext با فرآیند رمزنگاری (Encryption) تبدیل کنید. به همین ترتیب، میتوانید Ciphertext را با رمزگشایی (Decryption) با استفاده از الگوریتم رمزنگاری و کلیدی که برای ساخت پیام اصلی استفاده شده، به Plaintext تبدیل کنید.
میتوانید از تابع هش (Hash) برای نگاشت مجموعهای از بایتها با اندازه دلخواه به مجموعهای محدود و نسبتاً یکتای بایتها استفاده کنید. یک تابع هش رمزنگاری خوب باید از Salt استفاده کند — رشتهای از بیتهای تصادفی یا شبهتصادفی که با کلید یا رمز عبور متصل میشود. میتوانید امنیت را با معرفی یک تغییره رمزنگارانه اضافی با استفاده از بردار مقداردهی اولیه (Initialization Vector - IV) برای رمزنگاری توالی بلوک Plaintext افزایش دهید.
نکته: قطعات کد ارائهشده در این مقاله برای نشان دادن نحوه عملکرد API های جاوا در سطح بالا هستند. برای شفافیت، گاهی سادهسازی شدهاند. امنیت موضوع پیچیدهای است و همیشه منحصربهفرد نیازهای شماست، پس همیشه با متخصصان امنیت خود درباره نیازهای خاص خود مشورت کنید.
رمزنگاری جاوا بر اساس استانداردهای بینالمللی تعریفشده است که به پلتفرمهای مختلف اجازه عملکرد میدهند. از جمله این استانداردها:
چشمانداز امنیتی بهطور مستمر تکامل مییابد. مثلاً الگوریتمهای قویتر معرفی میشوند و الگوریتمهای قدیمیتر کمامنیتتر تلقی میشوند. Oracle JDK بهطور منظم برای مقابله با این تغییرات و حفظ امنیت پلتفرم جاوا بهروزرسانی میشود.
معماری رمزنگاری جاوا (Java Cryptography Architecture - JCA) فریمورکی برای کار با رمزنگاری با زبان برنامهنویسی جاوا است و بخشی از Java Security API محسوب میشود. اهداف آن شامل استقلال و قابلیت گسترش الگوریتمها، قابلیت همکاری و پیادهسازی مستقل از ارائهدهندگان امنیتی است.
JCA شامل کلاسهای Engine است که با نوع خاصی از سرویس رمزنگاری از طریق عملیات رمزنگاری مانند رمزنگاری، امضا، Message Digest و غیره، کلیدها و پارامترهای الگوریتم و KeyStore ها یا گواهینامهها تعامل میکنند.
JDK شامل پیادهسازیهای واقعی رمزنگاری برای مجموعهای از ارائهدهندگان مانند Sun، SunRSASign، SunJCE و غیره است. برای استفاده از JCA، برنامه نوع خاصی از شیء (مانند MessageDigest) و الگوریتم یا سرویس خاصی (مانند SHA-256) را درخواست میدهد و پیادهسازی را از یکی از ارائهدهندگان نصبشده دریافت میکند.
شکل ۱: درخواست اشیا برای ProviderC provider.MessageDigest.getInstance("SHA-256", "ProviderC")
برای دریافت لیست ارائهدهندگان نصبشده، java.security.Security.getProviders() را فراخوانی کنید. کد زیر را میتوانید در JShell اجرا کنید تا لیست الگوریتمهای رمزنگاری موجود هر ارائهدهنده را چاپ کنید:
jshell>
...> import java.security.Security;
...> import java.util.Set;
...> import java.util.TreeSet;
...>
...> Set<String> algos = new TreeSet<>();
...> for (Provider provider : Security.getProviders()){
...> Set <Provider.Service> service = provider.getServices();
...> service.stream().map(Provider.Service::getAlgorithm).forEach(algos::add);
...> }
...> algos.forEach(IO::println);
...>
algos ==> []
1.2.840.113554.1.2.2
1.3.6.1.5.5.2
AES
AES/GCM/NoPadding
AES/KW/NoPadding
AES/KW/PKCS5Padding
AES/KWP/NoPadding
AES_128/OFB/NoPadding
AES_192/CBC/NoPadding
// list was truncated for display purposes
برخی نمونههای محبوب ارائهدهنده شامل: SunPKCS11، SunMSCAPI (Windows)، BouncyCastle، RSA JSAFE، SafeNet هستند. اگر ارائهدهنده مورد نظر در لیست چاپشده نیست، میتوانید آن را با مراحل زیر ثبت کنید:
CLASSPATH قرار دهید.security.provider.5=SunJCEII.Security.addProvider(java.security.Provider) و Security.insertProviderAt(java.security.Provider,int).حالا بیایید با جزئیات بیشتر نحوه استفاده از رمزنگاری/رمزگشایی در جاوا را بررسی کنیم.
هنگام کار با رمزنگاری دادهها، میتوانید از این مکانیزم کنترل امنیتی برای محافظت از سه نوع وضعیت داده استفاده کنید:
رمزنگاری برای هر سه وضعیت داده مهم است تا لایه محافظتی اضافی در برابر حملات ارائه دهد. دو روش رمزنگاری وجود دارد: متقارن (Symmetric) و نامتقارن (Asymmetric).
رمزنگاری متقارن یا کلید مشترک روشی است که در آن هر دو طرف یک کلید را به اشتراک میگذارند. بهعنوان مثال، فرستنده A میتواند پیامی را با کلید مشترک رمزنگاری کند و فقط دریافتکننده B میتواند با همان کلید آن را رمزگشایی کند.
شکل ۲: رمزنگاری متقارن
برای پیادهسازی رمزنگاری متقارن در جاوا ابتدا باید یک کلید مشترک تولید کنید:
public static SecretKey generateKey() throws NoSuchAlgorithmException {
KeyGenerator keygenerator = KeyGenerator.getInstance("AES");
keygenerator.init(128);
return keygenerator.generateKey();
}
در مثال بالا، ابتدا یک تولیدکننده کلید مخفی با الگوریتم AES ایجاد میکنید. سپس آن را برای اندازه کلید ۱۲۸ بیت مقداردهی اولیه کرده و در نهایت یک کلید مخفی تولید میکنید.
از JDK 19، اندازه پیشفرض الگوریتم AES از ۱۲۸ بیت به ۲۵۶ بیت افزایش یافته است (اگر توسط Crypto Policy مجاز باشد).
برای تقویت مکانیزم رمزنگاری/رمزگشایی میتوانید یک بردار مقداردهی اولیه (IV) با مقدار دلخواه مقداردهی کنید:
public static IvParameterSpec generateIv() {
byte[] initializationVector = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(initializationVector);
return new IvParameterSpec(initializationVector);
}
چون رمزنگاری متقارن بلوکی با طول ثابت از داده Plaintext را به بلوک Ciphertext تبدیل میکند، حالتهای مختلفی در Block Cipher دارد:
ECB (Electronic Code Book Mode)CBC (Cipher Block Chain Mode)CCM (Counter/CBC Mode)CFB (Cipher Feedback Mode)OFB/OFBx (Output Feedback)CTR (Counter mode)GCM (Galois/Counter Mode)KW (Key Wrap Mode)KWP (Key Wrap Padding Mode)PCBC (Propagating Cipher Block Chaining)میتوانید تمام حالتها و تبدیلهای پشتیبانیشده را در بخش Cipher مشخصات Java Security Standard Algorithm Names بررسی کنید. سپس Block Cipher را در متد رمزنگاری مشخص کنید:
public static byte[] encrypt(String input, SecretKey key, IvParameterSpec iv)
throws Exception {
Cipher cipher = Cipher.getInstance("AES/CFB8/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
return cipher.doFinal(input.getBytes(StandardCharsets.UTF_8));
}
برای تبدیل Ciphertext به Plaintext اصلی، باید از همان Block Cipher، کلید و IV استفاده کنید:
public static String decrypt(byte[] cipherText, SecretKey key, IvParameterSpec iv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CFB8/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] plainText = cipher.doFinal(cipherText);
return new String(plainText);
}
متود doFinal() روی Cipher، دادهها را در یک عملیات تکبخشی رمزنگاری یا رمزگشایی میکند، یا یک عملیات چندبخشی را تمام کرده و آرایهای از بایتها برمیگرداند.
بیایید این متدها را برای رمزنگاری و رمزگشایی یک پیام ترکیب کنیم:
public static void main(String[] args) throws Exception {
SecretKey symmetricKey = generateKey();
IvParameterSpec iv = generateIv();
// Takes input from the keyboard
Scanner message = new Scanner(System.in);
String plainText = message.nextLine();
message.close();
// Encrypt the message using the symmetric key
byte[] cipherText = encrypt(plainText, symmetricKey, iv);
IO.println("The encrypted message is: " + cipherText);
// Decrypt the encrypted message
String decryptedText = decrypt(cipherText, symmetricKey, iv);
IO.println( "Your original message is: " + decryptedText);
}
رمزنگاری متقارن گزینه معتبری است اگر به روش رمزنگاری کمهزینه نیاز دارید، چون فقط به ایجاد یک کلید کوتاه (۴۰-۵۱۲ بیت) نیاز دارد که در دسترس فرستنده و دریافتکننده است.
رمزنگاری نامتقارن از یک جفت کلید مرتبط ریاضی استفاده میکند: یکی برای رمزنگاری و دیگری برای رمزگشایی.
شکل ۳: رمزنگاری نامتقارن
در چنین سیستمی، A میتواند پیامی را با کلید عمومی B رمزنگاری کند، اما فقط کلید خصوصی B میتواند آن را رمزگشایی کند.
برای پیادهسازی رمزنگاری نامتقارن در جاوا ابتدا باید یک جفت کلید (عمومی و خصوصی) با KeyPairGenerator تولید کنید:
public static KeyPair generateRSAKKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(3072);
return keyPairGenerator.generateKeyPair();
}
از JDK 19 به بعد، اندازه پیشفرض کلید الگوریتمهای RSA، RSASSA-PSS و DH از ۲۰۴۸ بیت به ۳۰۷۲ بیت افزایش یافته است.
متد رمزنگاری با کلید عمومی:
public static byte[] encrypt(String plainText, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
}
متد رمزگشایی با کلید خصوصی:
public static String decrypt(byte[] cipherText, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] result = cipher.doFinal(cipherText);
return new String(result);
}
برنامه شبیهسازی رمزنگاری/رمزگشایی نامتقارن:
public static void main(String[] args) throws Exception {
KeyPair keypair = generateRSAKKeyPair();
// takes input from the keyboard
Scanner message = new Scanner(System.in);
IO.print("Enter the message you want to encrypt using RSA: ");
String plainText = message.nextLine();
message.close();
byte[] cipherText = encrypt(plainText, keypair.getPublic());
IO.print("The encrypted text is: ");
IO.println(HexFormat.of().formatHex(cipherText));
String decryptedText = decrypt(cipherText, keypair.getPrivate());
IO.println("The decrypted text is: " + decryptedText);
}
میتوانید اصالت فرستنده و یکپارچگی پیام را با هش کردن پیام با MessageDigest تضمین کنید. برای پیادهسازی این کار، Digest پیام را ایجاد کرده و آن را با کلید خصوصی رمزنگاری کنید (این همان امضای دیجیتال است):
public static byte[] generateDigitalSignature(byte[] plainText, PrivateKey privateKey) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] messageHash = md.digest(plainText);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(messageHash);
}
برای تأیید اصالت پیام و فرستنده از کلید عمومی استفاده کنید:
public static boolean verify(byte[] plainText, byte[] digitalSignature, PublicKey publicKey) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hashedMessage = md.digest(plainText);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] decryptedMessageHash = cipher.doFinal(digitalSignature);
return Arrays.equals(decryptedMessageHash, hashedMessage);
}
فراخوانی نمونه:
public static void main(String[] args) throws Exception{
byte[] digitalSignature = generateDigitalSignature(plainText.getBytes(), keypair.getPrivate());
IO.println("Signature Value: " + HexFormat.of().formatHex(digitalSignature));
IO.println("Verification: " + verify(plainText.getBytes(), digitalSignature, keypair.getPublic()));
}
تبریک! نحوه پشتیبانی JCA از رمزنگاری در جاوا و پیادهسازی مکانیزمهای پایه رمزنگاری و رمزگشایی با Java Security API را آموختید.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/