JavaRush /Blog Java /Random-VI /Kiến trúc mật mã Java: Giới thiệu đầu tiên
Viacheslav
Mức độ

Kiến trúc mật mã Java: Giới thiệu đầu tiên

Xuất bản trong nhóm
Bảo mật trao đổi dữ liệu là một trong những đặc tính quan trọng nhất của các ứng dụng hiện đại. Từ xa xưa, con người đã nghĩ ra những phương pháp xảo quyệt, cùng với sự phát triển của nhân loại đã trở thành toàn bộ ngành khoa học về Mật mã học. Đương nhiên, Java không đứng ngoài cuộc và cung cấp cho các nhà phát triển Kiến trúc mật mã Java (JCA). Đánh giá này sẽ đưa ra ý tưởng đầu tiên về cách thức hoạt động của nó.

Lời nói đầu

Tôi đề nghị du hành ngược thời gian. Trước mắt chúng ta là La Mã cổ đại. Và trước mặt chúng tôi là Gaius Julius Caesar, người gửi một thông điệp tới các chỉ huy của mình. Hãy xem trong tin nhắn này có gì:
Kiến trúc mật mã Java: Giới thiệu lần đầu - 2
Điều này có thể có nghĩa là gì: "ЕСКЕУГЬГМХИФЯ Е УЛП"? Hãy mở Java Online Compiler, ví dụ: repl.it
class Main {
  public static void main(String[] args) {
    String code = "ЕСКЕУГЬГМХИФЯ Е УЛП";
    for (char symbol : code.toCharArray()) {
      if (symbol != ' ') {
        symbol = (char) (symbol - 3);
      }
      System.out.print(symbol);
    }
  }
}
Trước mắt chúng ta là cách thực hiện đơn giản nhất của Mật mã Caesar. Theo tác phẩm của nhà sử học La Mã cổ đại Suetonius có tựa đề “Cuộc đời của mười hai Caesars”, đây chính xác là cách Caesar mã hóa các thông điệp gửi đến các tướng lĩnh của mình. Và đây là một trong những tài liệu tham khảo cổ xưa nhất về việc sử dụng một thứ như Mật mã . Từ "mật mã" xuất phát từ các từ Hy Lạp cổ đại "ẩn" và "viết", tức là. đó là khoa học về kỹ thuật bảo mật. Java có hỗ trợ riêng cho mật mã và nó được gọi là Kiến trúc mật mã Java (JCA). Bạn có thể tìm thấy mô tả này trong tài liệu chính thức của Oracle - " Kiến trúc mã hóa Java (JCA) ". Tôi khuyên bạn nên xem xét những cơ hội mà chúng tôi có được nhờ JCA.
Kiến trúc mật mã Java: Giới thiệu lần đầu - 3

J.C.A.

Như chúng ta đã tìm hiểu trước đây, Java cung cấp Kiến trúc mật mã Java (JCA) để làm việc với mật mã. Kiến trúc này chứa một API (tức là một bộ giao diện nhất định) và các nhà cung cấp (triển khai chúng):
Kiến trúc mật mã Java: Giới thiệu lần đầu - 4
Như tài liệu nói, " Nền tảng Java bao gồm một số nhà cung cấp tích hợp sẵn ". Nghĩa là, nền tảng Java cung cấp một tập hợp các nhà cung cấp tích hợp có thể được mở rộng nếu cần thiết. Bạn có thể tự mình nhìn thấy điều này:
import java.security.Provider;
import java.security.Security;
class Main {
  public static void main(String[] args) {
    Provider[] providers = Security.getProviders();
    for (Provider p : providers) {
      System.out.println(p.getName());
    }
  }
}
Đăng ký nhà cung cấp bên thứ ba rất dễ dàng. Ví dụ: Security.addProvider(new BouncyCastleProvider()); Ví dụ này kết nối một trong những nhà cung cấp nổi tiếng nhất - BouncyCastle . Nhưng trong bài đánh giá này, chúng tôi sẽ chỉ sử dụng các công cụ cơ bản, không có thư viện của bên thứ ba. Tài liệu chính của chúng tôi: " Kiến trúc mật mã Java (JCA) ". Hiểu cách hoạt động của JCA sẽ giúp bạn dễ dàng hiểu hơn về các công nghệ mà JCA này được sử dụng tích cực. Ví dụ: HTTPS (xem phần " Từ HTTP đến HTTPS ").
Kiến trúc mật mã Java: Giới thiệu lần đầu - 5

Tóm lược thông điệp

Điều đầu tiên được đề cập trong tài liệu JCA là MessageDigest. Nói chung, Thông báo trong tiếng Nga sẽ giống nhau - thông báo có nghĩa tương ứng với “bản tóm tắt”. Nhưng trong mật mã, một bản tóm tắt là một tổng băm. Bạn cũng có thể dễ dàng nhớ rằng trong tiếng Anh Digest cũng có thể được dịch là tóm tắt. Bạn có thể tìm thêm thông tin chi tiết trong tài liệu JCA ở phần " MessageDigest ". Như tài liệu cho biết, MessageDigest tạo ra kết quả có kích thước cố định được gọi là thông báo hoặc hàm băm. Băm là hàm một chiều, tức là nếu chúng ta băm thứ gì đó, thì từ kết quả (tức là từ hàm băm), chúng ta không thể lấy được nguồn ban đầu. Nhưng nếu các đối tượng giống hệt nhau được băm (ví dụ: các chuỗi ký tự giống hệt nhau) thì hàm băm của chúng phải khớp nhau. Như đã nêu trong tài liệu, hàm băm như vậy đôi khi còn được gọi là “tổng kiểm tra” hoặc “dấu vân tay kỹ thuật số” của dữ liệu. Việc băm có thể được thực hiện bằng các thuật toán khác nhau. Các thuật toán có sẵn có thể được xem trong tài liệu " Tài liệu tên thuật toán tiêu chuẩn kiến ​​trúc mã hóa Java cho JDK 8 ". Hãy thực hiện băm và in hàm băm ra bàn điều khiển:
import javax.xml.bind.DatatypeConverter;
import java.security.*;
public class Main {
  public static void main(String[] args) {
    try {
      MessageDigest digester = MessageDigest.getInstance("SHA-512");
      byte[] input = "Secret string".getBytes();
      byte[] digest = digester.digest(input);
      System.out.println(DatatypeConverter.printHexBinary(digest));
    } catch (NoSuchAlgorithmException e) {
      throw new IllegalStateException(e);
    }
  }
}
Việc băm có thể hữu ích, chẳng hạn như khi lưu trữ mật khẩu. Vì hàm băm của mật khẩu đã nhập có thể được kiểm tra dựa trên hàm băm đã lưu trước đó. Nếu giá trị băm khớp thì mật khẩu cũng khớp. Để băm an toàn hơn nữa, một khái niệm gọi là “muối” được sử dụng. Salt có thể được triển khai bằng lớp SecureRandom . Trước khi thực hiện phương thức phân loại, hãy mô tả việc thêm "muối":
byte[] salt = new byte[16];
SecureRandom.getInstanceStrong().nextBytes(salt);
digester.update(salt);
Nhưng hàm băm là hàm một chiều. Nhưng nếu bạn muốn có thể mã hóa và giải mã thì sao?
Kiến trúc mật mã Java: Giới thiệu lần đầu - 6

Mật mã khóa đối xứng

Mã hóa đối xứng là mã hóa sử dụng cùng một khóa để mã hóa và giải mã. Để sử dụng mã hóa đối xứng, chúng ta cần có khóa. Để có được nó, chúng tôi sử dụng KeyGenerator . Ngoài ra, chúng ta sẽ cần một lớp đại diện cho mật mã ( Cipher ). Như đã nêu trong tài liệu JCA trong phần “ Tạo đối tượng mật mã ”, để tạo Mật mã, bạn cần chỉ định không chỉ một thuật toán mà còn cả một “sự biến đổi” trong dòng. Mô tả chuyển đổi trông như thế này: "thuật toán/chế độ/phần đệm":
  • Thuật toán : ở đây chúng ta xem xét các tên tiêu chuẩn cho “ Thuật toán mật mã (Mã hóa) ”. Nên sử dụng AES.
  • Chế độ : chế độ mã hóa. Ví dụ: ECB hoặc CBC (chúng ta sẽ nói về vấn đề này sau)
  • Thụt lề/Tách : Mỗi khối dữ liệu được mã hóa riêng biệt. Tham số này xác định lượng dữ liệu được tính là 1 khối.
Ví dụ: thực hiện phép biến đổi sau: "AES/ECB/PKCS5Padding". Tức là thuật toán mã hóa là AES, chế độ mã hóa là ECB (viết tắt của Electronic Codebook), kích thước khối là PKCS5Padding. PKCS5Padding nói rằng kích thước của một khối là 2 byte (16 bit). Chế độ mã hóa Sổ mã điện tử bao gồm mã hóa tuần tự từng khối:
Kiến trúc mật mã Java: Giới thiệu lần đầu - 7
Nó có thể trông giống như thế này trong mã:
import javax.xml.bind.DatatypeConverter;
import javax.crypto.*;
import java.security.Key;
public class Main {
  public static void main(String[] args) throws Exception {
    String text = "secret!!secret!!secret!!secret!!";
    // Generate new key
    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    keygen.init(256);
    Key key = keygen.generateKey();
    // Encrypt with key
    String transformation = "AES/ECB/PKCS5Padding";
    Cipher cipher = Cipher.getInstance(transformation);
    cipher.init(Cipher.ENCRYPT_MODE, key);
    byte[] encrypted = cipher.doFinal(text.getBytes());
    System.out.println(DatatypeConverter.printHexBinary(encrypted));
    // Decrypt with key
    cipher.init(Cipher.DECRYPT_MODE, key);
    String result = new String(cipher.doFinal(encrypted));
    System.out.println(result);
  }
}
Nếu chúng tôi thực hiện, chúng tôi sẽ mong đợi được lặp lại, bởi vì chúng tôi đã chỉ định 32 ký tự. Các ký tự này tạo thành 2 khối 16 bit:
Kiến trúc mật mã Java: Giới thiệu lần đầu - 8
Để tránh lặp lại trong trường hợp này, bạn nên sử dụng chế độ khác - Chuỗi khối mã hóa (CBC). Chế độ này giới thiệu khái niệm về Vector khởi tạo (được biểu thị bằng lớp IvParameterSpec). Và cũng nhờ chế độ này mà kết quả tạo khối cuối cùng sẽ được dùng để tạo khối tiếp theo:
Kiến trúc mật mã Java: Giới thiệu lần đầu - 9
Bây giờ chúng ta hãy viết mã này:
import javax.xml.bind.DatatypeConverter;
import javax.crypto.*;
import java.security.*;
import javax.crypto.spec.IvParameterSpec;
public class Main {
  public static void main(String[] args) throws Exception {
    // Initialization Vector
    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] rnd = new byte[16];
    random.nextBytes(rnd);
    IvParameterSpec ivSpec = new IvParameterSpec(rnd);
    // Prepare key
    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    keygen.init(256);
    Key key = keygen.generateKey();
    // CBC
    String text = "secret!!secret!!secret!!secret!!";
    String transformation = "AES/CBC/PKCS5Padding";
    Cipher cipher = Cipher.getInstance(transformation);
    cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
    byte[] enc = cipher.doFinal(text.getBytes());
    System.out.println(DatatypeConverter.printHexBinary(enc));
    // Decrypt
    cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
    String result = new String(cipher.doFinal(enc));
    System.out.println(result);
  }
}
Như chúng ta thấy, kết quả là chúng ta không thấy các khối mật mã lặp lại. Vì lý do này, chế độ ECB không được khuyến khích, bởi vì giúp bạn có thể thấy sự lặp lại và sử dụng kiến ​​thức này để giải mã. Để biết thêm thông tin về ECB và CBC, tôi khuyên bạn nên đọc tài liệu: “ Chế độ sổ mã điện tử ”. Nhưng mã hóa đối xứng có một vấn đề rõ ràng - bạn cần bằng cách nào đó chuyển khóa từ người mã hóa sang người mã hóa. Và dọc theo đường dẫn này, khóa này có thể bị chặn và sau đó có thể chặn dữ liệu. Và mã hóa bất đối xứng được thiết kế để giải quyết vấn đề này.
Kiến trúc mật mã Java: Giới thiệu lần đầu - 10

Mã hóa bất đối xứng

Mã hóa bất đối xứng hay Mật mã khóa công khai là phương pháp mã hóa sử dụng một cặp khóa: khóa riêng (được giữ bí mật với mọi người) và khóa chung (có sẵn cho công chúng). Sự tách biệt này là cần thiết để trao đổi khóa công khai một cách an toàn giữa các bên trao đổi thông tin, đồng thời giữ an toàn cho khóa bí mật. Khi tạo cặp khóa, KeyGenerator không còn đủ nữa mà chúng ta cần KeyPairGenerator . Hãy xem một ví dụ:
import javax.crypto.*;
import java.security.*;
public class Main {
  public static void main(String[] args) throws Exception {
    KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
    generator.initialize(1024);
    KeyPair keyPair = generator.generateKeyPair();
    // Encrypt with PRIVATE KEY
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
    byte[] data = cipher.doFinal("Hello!".getBytes());
    // Decrypt with PUBLIC KEY
    cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
    byte[] result = cipher.doFinal(data);
    System.out.println(new String(result));
  }
}
Điều quan trọng cần hiểu ở đây là khi sử dụng mã hóa bất đối xứng, chúng ta luôn sử dụng KeyPair để sử dụng một khóa để mã hóa và một khóa khác để giải mã. Nhưng bởi vì Mục đích của mã hóa là chỉ người nhận mới có thể giải mã nó; nó được mã hóa bằng khóa chung và chỉ được giải mã bằng khóa riêng.
Kiến trúc mật mã Java: Lần đầu làm quen - 11

Chữ ký số

Như chúng ta đã thấy ở trên, khi biết khóa chung, bạn có thể gửi dữ liệu để chỉ chủ sở hữu khóa riêng mới có thể giải mã được. Nghĩa là, bản chất của mã hóa bất đối xứng là bất kỳ ai cũng mã hóa, nhưng chỉ chúng ta đọc. Ngoài ra còn có một quy trình ngược lại - chữ ký số, được biểu thị bằng lớp Signature . Chữ ký số có thể sử dụng các thuật toán sau: " Thuật toán chữ ký ". Tài liệu của JCA khuyên bạn nên xem xét kỹ hơn hai loại này: DSAwithMD5 và RSAwithMD5 Cái nào tốt hơn DSA hoặc RSA và sự khác biệt của chúng là gì, bạn có thể đọc ở đây: " Cái nào hoạt động tốt nhất để truyền tệp được mã hóa - RSA hoặc DSA? ". Hoặc đọc các cuộc thảo luận tại đây: " RSA vs. DSA cho các khóa xác thực SSH ". Vì vậy, chữ ký số. Như trước đây, chúng ta sẽ cần một KeyPair và một lớp Signature mới. Nếu cho đến nay bạn đã thử nghiệm các trình biên dịch trực tuyến thì ví dụ sau đây có thể hơi khó khăn đối với chúng. Ví dụ của tôi chỉ chạy ở đây: rextester.com . Chúng tôi nhập các lớp chúng tôi cần:
import javax.crypto.*;
import java.security.*;
Chúng ta cũng sẽ viết lại phương thức main:
public static void main(String[] args) throws Exception {
    // Generate keys
    KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
    SecureRandom random = SecureRandom.getInstanceStrong();
    generator.initialize(2048, random);
    KeyPair keyPair = generator.generateKeyPair();
    // Digital Signature
    Signature dsa = Signature.getInstance("SHA256withRSA");
    dsa.initSign(keyPair.getPrivate());
    // Update and sign the data
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
    byte[] data = cipher.doFinal("Hello!".getBytes());
    dsa.update(data);
    byte[] signature = dsa.sign();
    // Verify signature
    dsa.initVerify(keyPair.getPublic());
    dsa.update(data);
    boolean verifies = dsa.verify(signature);
    System.out.println("Signature is ok: " + verifies);
    // Decrypt if signature is correct
    if (verifies) {
      cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
      byte[] result = cipher.doFinal(data);
      System.out.println(new String(result));
    }
}
Đây là cách chữ ký số hoạt động. Chữ ký số là một chủ đề thú vị. Tôi khuyên bạn nên xem báo cáo về chủ đề này:
Kiến trúc mật mã Java: Lần đầu làm quen - 12
Ở trên chúng ta đã thấy cách các bên trao đổi dữ liệu. Không có giao diện chuẩn nào cho tương tác này được cung cấp trong JCA sao? Hóa ra là có. Hãy nhìn vào nó.
Kiến trúc mật mã Java: Lần đầu làm quen - 13

Thỏa thuận chính

Kiến trúc Mật mã Java giới thiệu một công cụ quan trọng - Thỏa thuận khóa là một giao thức. Nó được đại diện bởi lớp KeyAgreement . Như đã nêu trong tài liệu JCA, giao thức này cho phép nhiều bên đặt cùng một khóa mật mã mà không chia sẻ bất kỳ thông tin bí mật nào giữa các bên. Nghe có vẻ lạ phải không? Sau đó hãy xem một ví dụ:
// 1. Одна из сторон (Алиса) генерирует пару ключей. Encoded публичный ключ отдаёт.
KeyPairGenerator generator = KeyPairGenerator.getInstance("DH");
KeyPair aliceKeyPair = generator.generateKeyPair();
byte[] alicePubKeyEncoded = aliceKeyPair.getPublic().getEncoded();

// 2. Другая сторона (например, Боб) получает открытый ключ Алисы
KeyFactory bobKeyFactory = KeyFactory.getInstance("DH");
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(alicePubKeyEncoded);
PublicKey alicePubKey = bobKeyFactory.generatePublic(x509KeySpec);
// Параметры, которые использовала Алиса при генерации ключей
DHParameterSpec dhParamFromAlicePubKey = ((DHPublicKey)alicePubKey).getParams();
// Создаёт свою пару ключей. Отдаёт свой Encoded открытый ключ
KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
bobKpairGen.initialize(dhParamFromAlicePubKey);
KeyPair bobKeyPair = bobKpairGen.generateKeyPair();
byte[] bobPubKeyEncoded = bobKeyPair.getPublic().getEncoded();

Теперь, у Алисы есть открытый ключ Боба, а у Боба есть открытый ключ Алисы. What дальше?
Как сказано в documentации JCA, у нас есть инструмент KeyAgreement, https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html#KeyAgreement который позволяет установить одинаковые ключи шифрования без необходимости обмениваться секретной информацией (т.е. без обмена private key). Соглашение выглядит следующим образом:
// 3. Соглашение по протоколу Диффи-Хеллмана (Diffie–Hellman, DH)
KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");
aliceKeyAgree.init(aliceKeyPair.getPrivate());
// Алиса на основе ключа боба и своего private key создаёт общий shared ключ
KeyFactory aliceKeyFactory = KeyFactory.getInstance("DH");
x509KeySpec = new X509EncodedKeySpec(bobPubKeyEncoded);
PublicKey bobPubKey = aliceKeyFactory.generatePublic(x509KeySpec);
aliceKeyAgree.doPhase(bobPubKey, true);
byte[] aliceSharedSecret = aliceKeyAgree.generateSecret();
SecretKeySpec aliceAesKey = new SecretKeySpec(aliceSharedSecret, 0, 16, "AES");
// Боб на основе ключа Алисы и своего private key создаёт общий shared ключ
KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");
bobKeyAgree.init(bobKeyPair.getPrivate());
bobKeyAgree.doPhase(alicePubKey, true);
byte[] bobSharedSecret = bobKeyAgree.generateSecret();
SecretKeySpec bobAesKey = new SecretKeySpec(bobSharedSecret, 0, 16, "AES");
// Общий ключ у Алисы и Боба одинаков
System.out.println("Shared keys are equals: " + Arrays.equals(aliceSharedSecret, bobSharedSecret));

Далее Боб и Алиса, используя общий ключ, про который больше никто не знает, обмениваются зашифрованными данными:
// 4. Боб шифрует сообщение для Алисы
Cipher bobCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
bobCipher.init(Cipher.ENCRYPT_MODE, bobAesKey);
byte[] ciphertext = bobCipher.doFinal("Hello, Alice!".getBytes());
// Передаёт Алисе параметры, с которыми выполнялась шифровка
byte[] encodedParamsFromBob = bobCipher.getParameters().getEncoded();

// 5. Алиса принимает сообщение и расшифровывает его
AlgorithmParameters aesParams = AlgorithmParameters.getInstance("AES");
aesParams.init(encodedParamsFromBob);
Cipher aliceCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
aliceCipher.init(Cipher.DECRYPT_MODE, aliceAesKey, aesParams);
byte[] recovered = aliceCipher.doFinal(ciphertext);
System.out.println(new String(recovered));
Ví dụ này được lấy từ ví dụ tài liệu của JCA: " Trao đổi khóa Diffie-Hellman giữa 2 bên ". Đây gần giống mã hóa bất đối xứng trong Kiến trúc mật mã Java sử dụng giao thức Thỏa thuận khóa. Để biết thêm thông tin về mã hóa bất đối xứng, các video được đề xuất:
Kiến trúc mật mã Java: Lần đầu làm quen - 14

Chứng chỉ

Chà, đối với món tráng miệng, chúng ta vẫn còn một thứ không kém phần quan trọng - giấy chứng nhận. Thông thường, chứng chỉ được tạo bằng tiện ích keytool đi kèm với jdk. Bạn có thể đọc thêm chi tiết, ví dụ, tại đây: " Tạo chứng chỉ SSL tự ký bằng lệnh Java keytool ". Bạn cũng có thể đọc hướng dẫn sử dụng từ Oracle. Ví dụ: ở đây: " Sử dụng keytool để tạo chứng chỉ máy chủ ". Ví dụ: hãy sử dụng Trình biên dịch trực tuyến Java Tutorialspoint :
import sun.security.tools.keytool.CertAndKeyGen;
import sun.security.x509.*;
import java.security.cert.*;
import java.security.*;
// Compiler args: -XDignore.symbol.file
public class Main {
  public static void main(String[] args) throws Exception {
    CertAndKeyGen certGen = new CertAndKeyGen("RSA", "SHA256WithRSA", null);
    // generate it with 2048 bits
    certGen.generate(2048);
    PrivateKey privateKey = certGen.getPrivateKey();
    X509Key publicKey = certGen.getPublicKey();
    // prepare the validity of the certificate
    long validSecs = (long) 365 * 24 * 60 * 60; // valid for one year
    // enter your details according to your application
    X500Name principal = new X500Name("CN=My Application,O=My Organisation,L=My City,C=DE");
    // add the certificate information, currently only valid for one year.
    X509Certificate cert = certGen.getSelfCertificate(principal, validSecs);
    // Public Key from Cert equals Public Key from generator
    PublicKey publicKeyFromCert = cert.getPublicKey();
    System.out.println(publicKeyFromCert.equals(publicKey));
  }
}
Như chúng ta có thể thấy, chứng chỉ cung cấp khả năng cung cấp khóa chung. Phương pháp này có một nhược điểm - chúng tôi sử dụng sun.security, được coi là rủi ro, bởi vì... gói này không phải là một phần của API Java công khai. Đó là lý do tại sao trong quá trình biên dịch cần phải chỉ định tham số - XDignore.symbol.file. Có một cách khác - tạo chứng chỉ theo cách thủ công. Nhược điểm là nó sử dụng API nội bộ không được ghi lại. Tuy nhiên, nó rất hữu ích để biết về nó. Ở mức tối thiểu, vì có thể thấy rõ cách sử dụng thông số kỹ thuật RFC-2459: “ Cơ sở hạ tầng khóa công khai Internet X.509 ”. Đây là một ví dụ:
// 1. Генерируем пару ключей
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(4096);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 2. Определяем данные сертификата
// Определяем срок действия сертификата
Date from = new Date();
Date to = new Date(from.getTime() + 365 * 1000L * 24L * 60L * 60L);
CertificateValidity interval = new CertificateValidity(from, to);
// Определяем subject name, т.е. Name того, с чем ассоциирован публичный ключ
// CN = Common Name. Через точку с запятой могут быть указаны также другие атрибуты
// См. https://docs.oracle.com/cd/E24191_01/common/tutorials/authz_cert_attributes.html
X500Name owner = new X500Name("cn=Unknown");
// Уникальный в пределах CA, т.е. Certificate Authority (тот, кто выдаёт сертификат) номер
BigInteger number = new BigInteger(64, new SecureRandom());
CertificateSerialNumber serialNumber = new CertificateSerialNumber(number);
// Определяем алгоритм подписи сертификата
AlgorithmId algorithmId = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
CertificateAlgorithmId certificateAlgorithmId = new CertificateAlgorithmId(algorithmId);
// 3. По подготовленной информации создаём сертификат
X509CertInfo info = new X509CertInfo();
info.set(X509CertInfo.VALIDITY, interval);
info.set(X509CertInfo.SERIAL_NUMBER, serialNumber);
info.set(X509CertInfo.SUBJECT, owner);
info.set(X509CertInfo.ISSUER, owner);
info.set(X509CertInfo.KEY, new CertificateX509Key(keyPair.getPublic()));
info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
info.set(X509CertInfo.ALGORITHM_ID, certificateAlgorithmId);
// 4. Подписываем сертификат
X509CertImpl certificate = new X509CertImpl(info);
certificate.sign(keyPair.getPrivate(), "SHA256withRSA");
// 5. Проверка сертификата
try {
	// В случае ошибки здесь будет брошено исключение. Например: java.security.SignatureException
	certificate.verify(keyPair.getPublic());
} catch (Exception e) {
	throw new IllegalStateException(e);
}
Kiến trúc mật mã Java: Giới thiệu lần đầu - 15

Kho khóa (KeyStore)

Điều cuối cùng tôi muốn nói đến là kho khóa và chứng chỉ, được gọi là KeyStore. Rõ ràng là việc liên tục tạo ra các chứng chỉ và khóa là tốn kém và vô nghĩa. Vì vậy, chúng cần được lưu trữ bằng cách nào đó một cách an toàn. Có một công cụ cho việc này - KeyStore. Kho khóa được mô tả trong tài liệu JCA ở chương " Quản lý khóa ". API để làm việc với nó rất rõ ràng. Đây là một ví dụ nhỏ:
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
String alias = "EntityAlias";
java.security.cert.Certificate[] chain = {certificate};
keyStore.setKeyEntry(alias, keyPair.getPrivate(), "keyPassword".toCharArray(), chain);
// Загрузка содержимого (Private Key + Certificate)
Key key = keyStore.getKey(alias, "keyPassword".toCharArray());
Certificate[] certificateChain = keyStore.getCertificateChain(alias);
// Сохранение KeyStore на диск
File file = File.createTempFile("security_", ".ks");
System.out.println(file.getAbsolutePath());
try (FileOutputStream fos = new FileOutputStream(file)) {
	keyStore.store(fos, "keyStorePassword".toCharArray());
}
Như bạn có thể thấy trong ví dụ, nó được thực thi đầu tiên loadcho KeyStore. Nhưng trong trường hợp của chúng tôi, chúng tôi đã chỉ định thuộc tính đầu tiên là null, tức là không có nguồn cho KeyStore. Điều này có nghĩa là KeyStore được tạo trống để lưu thêm. Tham số thứ hai cũng là null, bởi vì chúng tôi đang tạo một KeyStore mới. Nếu chúng ta đang tải KeyStore từ một tệp thì chúng ta sẽ cần chỉ định mật khẩu tại đây (tương tự như phương thức KeyStore được gọi là store).

Điểm mấu chốt

Như vậy là chúng tôi đã cùng các bạn ôn lại các thao tác cơ bản và cơ bản nhất trong khuôn khổ Kiến trúc mật mã Java (còn gọi là JCA). Chúng ta đã biết mã hóa đối xứng và bất đối xứng là gì cũng như cách nó được triển khai trong JCA. Chúng ta đã thấy cách tạo chứng chỉ và chữ ký điện tử cũng như cách sử dụng chúng. Tất cả chỉ là những điều cơ bản, đằng sau đó còn rất nhiều điều phức tạp và thú vị hơn. Tôi hy vọng tài liệu đánh giá này sẽ hữu ích và sẽ khiến bạn quan tâm đến việc nghiên cứu sâu hơn về lĩnh vực này.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION